ReferenceContributor unable to Resolve PsiLiteralExpression References Contained Inside Spring Value Annotation

Hello,

This is my first time developing a plugin, so I'm somewhat lost.

I'm currently trying to implement a ReferenceContributor that will be able to resolve from a YAML path specified in a Java file to its corresponding value in the containing YAML file.

The contributor works fine for expressions that are in string literals i.e.

String value = "some.path.to.value"

but is unable to do the exact same thing when given in Spring's @Value annotation, i.e.:

@Value("some.path.to.value")

String value

I notice that it is never calling the resolve method in the Reference class whenever I attempt to ctrl + click the value contained in the @Value annotation.

For reference, I was able to successfully create a LineMarkerProvider that worked in both situations.

I'm not quite sure what I could be missing since I essentially copied and pasted from the Custom Language Support Tutorial.

 

Here is the ReferenceContributor:

package com.yamlplugin;

import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.*;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.meta.model.YamlReferenceType;
import org.jetbrains.yaml.psi.YAMLPsiElement;
import org.jetbrains.yaml.psi.impl.YAMLScalarTextImpl;

import java.util.List;

public class YamlReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class),
new PsiReferenceProvider() {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element,
@NotNull ProcessingContext
context) {
PsiLiteralExpression literalExpression = (PsiLiteralExpression) element;
String value = literalExpression.getValue() instanceof String ?
(String) literalExpression.getValue() : null;
if (value != null) {
YamlReference yamlReference = new YamlReference(element, new TextRange(1, value.length() + 1));
PsiReference[] psiReferences = new PsiReference[]{yamlReference};
return psiReferences;
}
return PsiReference.EMPTY_ARRAY;
}
});
}
}



Here is the Reference Class:

package com.yamlplugin;

import com.intellij.codeInsight.lookup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import org.jetbrains.annotations.*;
import org.jetbrains.yaml.psi.YAMLPsiElement;

import java.util.*;

public class YamlReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference {
private String key;
private String [] keyPath;

public YamlReference(@NotNull PsiElement element, TextRange textRange) {
super(element, textRange);
key = element.getText().substring(textRange.getStartOffset(), textRange.getEndOffset());
keyPath = FNMYamlPluginUtility.processStringLiteral(key);
}

@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
Project project = myElement.getProject();
final List<YAMLPsiElement> properties = FNMYamlPluginUtility.findProperties(project, keyPath);
List<ResolveResult> results = new ArrayList<ResolveResult>();
for (YAMLPsiElement property : properties) {
results.add(new PsiElementResolveResult(property));
}
return results.toArray(new ResolveResult[results.size()]);
}

@Nullable
@Override
public PsiElement resolve() {
ResolveResult[] resolveResults = multiResolve(false);
return resolveResults.length == 1 ? resolveResults[0].getElement() : null;
}

@NotNull
@Override
public Object[] getVariants() {
Project project = myElement.getProject();
List<YAMLPsiElement> properties = FNMYamlPluginUtility.findProperties(project);
List<LookupElement> variants = new ArrayList<LookupElement>();
for (final YAMLPsiElement property : properties) {
if (property.getText() != null && property.getText().length() > 0) {
variants.add(LookupElementBuilder.create(property).
withIcon(FNMYamlIcons.FILE).
withTypeText(property.getContainingFile().getName())
);
}
}
return variants.toArray();
}
}

 

For good measure, here is the Util Class I use:

package com.yamlplugin;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.yaml.YAMLFileType;
import org.jetbrains.yaml.YAMLUtil;
import org.jetbrains.yaml.psi.YAMLFile;
import org.jetbrains.yaml.psi.YAMLKeyValue;
import org.jetbrains.yaml.psi.YAMLPsiElement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class FNMYamlPluginUtility {

public static List<YAMLPsiElement> findProperties(Project project, String[] keys) {
List<YAMLPsiElement> result= null;
Collection<VirtualFile> virtualFiles =
FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, YAMLFileType.YML,
GlobalSearchScope.allScope(project));
for (VirtualFile virtualFile : virtualFiles) {
YAMLFile yamlFile = (YAMLFile) PsiManager.getInstance(project).findFile(virtualFile);
if (yamlFile != null) {
YAMLKeyValue keyValue = YAMLUtil.getQualifiedKeyInFile(yamlFile, keys);
if (keyValue != null) {
if(result == null)
{
result = new ArrayList<>();
}
result.add(keyValue);
}
}
}
return result != null ? result : Collections.<YAMLPsiElement>emptyList();
}

public static List<YAMLPsiElement> findProperties(Project project) {
List<YAMLPsiElement> result = new ArrayList<YAMLPsiElement>();
Collection<VirtualFile> virtualFiles =
FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, YAMLFileType.YML,
GlobalSearchScope.allScope(project));
for (VirtualFile virtualFile : virtualFiles) {
YAMLFile yamlFile = (YAMLFile) PsiManager.getInstance(project).findFile(virtualFile);
if (yamlFile != null) {
YAMLPsiElement[] properties = PsiTreeUtil.getChildrenOfType(yamlFile, YAMLPsiElement.class);
if (properties != null) {
Collections.addAll(result, properties);
}
}
}
return result;
}

public static String[] processStringLiteral(String path) {
String[] finalResult;
if(path.contains("$")) {
path = path.replace("$", "");
}
if(path.contains("{")) {
path = path.replace("{", "");

}
if(path.contains("}")) {
path = path.replace("}", "");

}
if(path.contains("."))
{
finalResult = path.split("\\.");
} else{
finalResult = new String[]{path};
}
return finalResult;
}
}

I've seen that many times people get the text range incorrect, but I haven't been able to modify it to get it to work.

Any help or direction would be greatly appreciated.


Thank you,


Coleman

3
2 comments

> This is my first time developing a plugin, so I'm somewhat lost.

I can't count how many times I was lost (completely) while trying to make sense of IntelliJ platform :)

By any chance, @... have you been able to resolve this issue?

0

Apparently I get emails when someone comments on these! I was not able to resolve the issue, but I found a work around. Instead of implementing the Ctrl+click functionality I instead had intellij display an icon whenever it was able to resolve a path. If you clicked the icon it would list all the YAML files where a reference to that string could be found. I don't remember how I did it and I've since left the company where the code is stored. I believe if you look in their tutorial guides they go over how to add something similar. Sorry I couldn't be of more help!

0

Please sign in to leave a comment.