Get usages in return from referenced element

Answered

PsiReferenceContributor implementation:

public class ImplementationStepReferenceContributor extends PsiReferenceContributor {

@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(literalExpression(), new ImplementationStepReferenceProvider());
}
}

PsiReferenceProvider:

public class ImplementationStepReferenceProvider extends PsiReferenceProvider {

@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext context) {
if (isStepCallLiteral(psiElement)) {
return new PsiReference[] { new ImplementationStepReference((PsiLiteralExpression) psiElement) };
}
return PsiReference.EMPTY_ARRAY;
}
}

PsiReferenceBase<PsiLiteralExpression>:

public class ImplementationStepReference extends PsiReferenceBase<PsiLiteralExpression> {

public ImplementationStepReference(@NotNull PsiLiteralExpression element) {
super(element);
}

@Override
public @Nullable PsiElement resolve() {
return SearchUtil.findSingleStepImplementation(myElement).orElse(null);
}
}

This code gives me a single reference to a method from a specific PsiLiteralExpression.

Example:

From some where I call translate method of class Specification:

public void steps() {
new Specification().translate("this is my step name example");
new Specification().translate("this is my step name example");
new Specification().translate("this is my step name example");
}

And my method that I bind must be annotated like this:

@Step("this is my step name example")
public void someMethod() {}

My implementation supports CTRL+ click onto literal on method call -> new Specification().translate("this is my step name example"); and it navigates me to a method successfully.

But how do I implement usages in return?

If I have a reference to a method at least in one place it shouldn't be popping a warning that it is not used anywhere but to provide 3 usages as in the example above in return.

Thanks,
Serhii

8 comments
Comment actions Permalink

Hi Serhii,

You can try implementing QueryExecutorBase, which will search for method references with custom names (value of @Step in your case). It can look like this (in Kotlin):

package com.example

import com.intellij.openapi.application.QueryExecutorBase
import com.intellij.psi.PsiReference
import com.intellij.psi.search.searches.MethodReferencesSearch.SearchParameters
import com.intellij.util.Processor

class YourMethodReferencesSearch : QueryExecutorBase<PsiReference, SearchParameters>(true) {

override fun processQuery(searchParams: SearchParameters, consumer: Processor<in PsiReference>) {
val methodName = "this is my step name example" // value of @Step annotation
searchParams.optimizer.searchWord(methodName, searchParams.effectiveSearchScope, true, searchParams.method)

}

}

and register it as:

<methodReferencesSearch implementation="com.example.YourMethodReferencesSearch"/>

Of course, your reference's isReferenceTo() should return true if asked for the searched method.

0
Comment actions Permalink

Karol Lewandowski It worked instantly, cool!

However search by word has limitations because it is compared by equals I suppose. What if I need to search by regexp instead?
I looked at method:

searchParams.optimizer.searchQuery

But it seems to be super difficult to compose and I am not sure this is the right way.

0
Comment actions Permalink

Hi Serhii,

This case is harder to handle. The Find Usages mechanism is based on word index, so occurrences of words will be processed. See:
https://plugins.jetbrains.com/docs/intellij/find-usages.html

If your regular expressions contain any words, then you can split them and invoke searchParams.optimizer.searchWord() for each word, and of course, in isReferenceTo() return true if the found element text matches the regexp.

 

0
Comment actions Permalink

Hi Karol Lewandowski

I've tried to implement FindUsagesProvider but looks like it requires Custom Language Lexer to be implemented and corresponding dependencies, isn't it?

Can't I do this without such chellenges?

This is what I've started with, just to understand if my FindUsagesProvider ever called:

public class FUP implements FindUsagesProvider {

public FUP() {
System.out.println("CREATED");
}

@Override
public WordsScanner getWordsScanner() {
return new SimpleWordsScanner();
}

@Override
public boolean canFindUsagesFor(@NotNull PsiElement psiElement) {
if (!(psiElement instanceof PsiMethod)) {
return false;
}
PsiMethod stepMethod = (PsiMethod) psiElement;
return stepMethod.hasAnnotation(STEP);
}

@Override
public @Nullable
@NonNls String getHelpId(@NotNull PsiElement psiElement) {
return FIND_METHOD_USAGES;
}

@Override
public @Nls
@NotNull String getType(@NotNull PsiElement psiElement) {
return "step definition";
}

@Override
public @Nls
@NotNull String getDescriptiveName(@NotNull PsiElement psiElement) {
PsiMethod stepMethod = (PsiMethod) psiElement;
return stepMethod.getName();
}

@Override
public @Nls
@NotNull String getNodeText(@NotNull PsiElement psiElement, boolean b) {
return this.getDescriptiveName(psiElement);
}
}

But when I trigger an action CTRL + click on method name only constructor is called and no other overriden methods.
What can be  wrong here?

0
Comment actions Permalink

Hi Serhii,

I don't think this is a way to go, or I don't get your idea.

FindUsagesProvider is intended to be implemented for custom languages. Java Find Usages Provider is implemented in the Java plugin: com.intellij.lang.java.JavaFindUsagesProvider.

Your provider is not called because JavaFindUsagesProvider is found before your provider as a one that supports finding methods, see:
https://github.com/JetBrains/intellij-community/blob/master/java/java-impl/src/com/intellij/lang/java/JavaFindUsagesProvider.java#L37

0
Comment actions Permalink

Karol Lewandowski
Sorry for misleading, let me make my goal clear.

So having method call:

myObject.translate("this is my step 'text' example");

And resolving method:

@Step("this is my step $name example")
public void someMethod() {
System.out.println("Hooray!);
}

I have a single reference on a literal myObject.translate("this is my step 'text' example"); which navigates me to a method (method name if you wish) ...public void someMethod() {...}

It is implemented through regex match of method annotation literal @Step("this is my step $name example"). In other words:

"this is my step 'anything' example"
"this is my step 'anythingElse' example"
"this is my step 'what is this' example"

matches the following

"this is my step $name example"

So my $name in annotation literal is a parameter that matches anything in single quotes.

This works perfectly.

Then, I want to make this vise-versa:

Clicking on a method name with CTRL:

@Step("this is my step $name example")
public void someMethod() {
System.out.println("Hooray!);
}

Show me usages that match the same pattern:

myObject.translate("this is my step 'anything' example");
myObject.translate("this is my step 'anythingElse' example");
myObject.translate("this is my step 'what is this' example");


Hope this is clear now. Looking forward for your reply!

Thank you,
Serhii

0
Comment actions Permalink

Thanks for the explanation.

I think that you can implement something very similar to my previous proposal, so extending the method reference search with additional words to search, with the difference that you can slightly modify the word to search by avoiding the dynamic part, e.g.:

  • Let's assume that you want to search for references matching "this is my step $name example"
  • Search for a substring "this is my step", so the string without the dynamic part and ending, as it is not required to include it because finding the starting string is enough. If your string started with the dynamic part, e.g., "$name lorem ipsum", then you could search for "lorem ipsum", so basically by any part of the "@Step" value that doesn't include the dynamic part. The goal here is to find potential usages. The actual usages will be confirmed by isReferenceTo().
  • In isReferenceTo() check if the potential usage matches the "@Step" value, e.g., by replacing the dynamic parts with ".*" and checking if the usage candidate matches such a regexp.
0
Comment actions Permalink

Karol Lewandowski
It worked like a charm, thank you so much for help provided. Topic can be closed ;)

0

Please sign in to leave a comment.