Code Completion - get context in which is method called

Hi,
I am developing plugin for code completion in PHP method parameters. I have following code:

public class OptionsCompletionContributor extends CompletionContributor {
    @Override
    public void fillCompletionVariants
(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
        PsiElement element = parameters.getPosition().getParent();
        
ParameterList parameterList = PsiTreeUtil.getParentOfType(element, ParameterList.class);
        if
(parameterList == null) {
            return;
        
}
        PsiElement[] givenParameters = parameterList.getParameters();
        
PsiElement context = parameterList.getContext();
        if
(context instanceof MethodReference){
            MethodReference method = (MethodReference) context;
            
ClassReference cls = (ClassReference) method.getClassReference();
            
            // here I need help

          
        }
    }
}

So I know method and class for example here:

<?php
class SomeClass {
     public static function someMethod($someParameter){
     }
     public static function someMethod2($someParameter){
     }
     public static function iDontCareAboutThisMethod($someParameter){
     }

}

I know it´s class "SomeClass", method "someMethod" or "someMethod2" and I know it´s first parameter as well. I am doing code completion for $someParameter in these methods.

But now what I need:

<?php
class Test {
     public function something(){
          SomeClass::someMethod("foo");
          SomeClass::iDontCareAboutThisMethod("something");
          SomeClass::someMethod2("bar");
          SomeClass::someMethod(/* here is my cursor and here and want code completion "foo" and "bar"*/);
     }
}

I need to know that I am in some method called "something" and I need to get all calls of "someMethod" and "someMethod2" methods and their parameters. So in this case I want to get List of "foo" and "bar".

Is there any possibility to do this?

Thanks for help!

9 comments
Comment actions Permalink

are you looking for parameters passed to someMethod / someMethod2

in the context of one function (in this example, function something())

?

0
Comment actions Permalink

Yes, this is exactly what I need.

0
Comment actions Permalink

assuming that your Psi Tree looks something like this:

CLASS
    FUNCTION
        SYMCL :: SYMMETHOD (PARAM*)

you could walk up & down Psi Tree

completion parameters -> PsiElement ->
    up to FUNCTION -> collect SYMMETHOD[] -> filter by name -> PARAM[]

does this makes any sense ?

0
Comment actions Permalink

I understand the principle but I don´t know how can I do it.
I have instance of CompletionParameters called parameters. I have Psi Element: PsiElement element = parameters.getPosition().getParent(); (is this right?)
But now I have no idea how to continue. How can I get instance of Function?

0
Comment actions Permalink

this seems ok:

PsiElement element = parameters.getPosition().getParent();

now you can call element.getParent().getParent()...  until you get up to Function PsiElement

then you could call PsiTreeUtil.collectElements(functionElement, PsiElementFilter) to find methods down in the tree.

from each method you could call PsiTreeUtil.collectElements(methodElement, PsiElementFilter) to find parameters.

then on each parameter call getText(), put them in a Set<String>, and once this set is ready, you could call this:

 
void updateResults(@NotNull Set<String> in, @NotNull CompletionResultSet out) {
   for (String str : in) {
      out.addElement(LookupElementBuilder.create(str));
   
}
}





try putting a breakpoint on

PsiElement element = parameters.getPosition().getParent();

and see what other (than getParent) methods are available on element

play with this a bit, then let me know if you are stuck. I may reply tomorrow (not tonight).

1
Comment actions Permalink

Thank you for your help, now I have last problem :)

My code:

PsiElement parentMethod = element.getParent().getParent().getParent().getParent();
PsiElement
[] elements = PsiTreeUtil.collectElements(parentMethod, psiElement -> true);

List<String> options = new ArrayList<>();

for
(PsiElement pes : elements){

     ParameterList params =  PsiTreeUtil.getChildOfType(pes, ParameterList.class);
     if
(params != null){
          PsiElement[] elementsParams = params.getParameters();
          PsiElement ctx = params.getContext();
          

          if
(ctx instanceof MethodReference){
               MethodReference methodReference = (MethodReference) ctx; // this is ok I can test name of method
               ClassReference
classReference = (ClassReference) methodReference.getClassReference(); // throws an exception
              if
(/*here I want test name of class*/){
                    for(PsiElement param : elementsParams){
                         String text = param.getText();
                         if
(text != null){
                              text = text.replace("\"","");
                              if
(text.length() > 0) options.add(text);
                    
     }
                    }
               }
          }
     }
}

for(String option : options){
     result.addElement(LookupElementBuilder.create(option));
}


I cannot get class reference because of following exception:
ClassCastException: com.jetbrains.php.lang.psi.elements.impl.MethodReferenceImpl cannot be cast to com.jetbrains.php.lang.psi.elements.ClassReference

Which way can I get reference to class? According to my first post I need to check that "someMethod" or "someMethod2" is only from "SomeClass".

0
Comment actions Permalink

to prevent the cast error, before casting an element, you could check its type first:

element instance of XSYMMETHOD
or (element.class.isAssignableFrom(XSYMMETHOD.class)
|| XSYMMETHOD.class.isAssignableFrom(element.class))


text of any PsiElement e.g. "someMethod" is PsiElement.getText()

to collect PsiElements of some class / IElementType but not other, tweak PsiElementFilter like this:

boolean isAccepted(PsiElement element){
    return element instance of XSYMMETHOD

//  or
    return el.getNode().getElementType().equals(XTypes.SYMMETHOD);
}


you could also create a TokenSet tokenSet by TokenSet.create(...)

and check it by tokenSet.contains(IElementType)

1
Comment actions Permalink

Thank you very much for your help!

My final working solution is test getText() method on PsiElement which represents method.

0

Please sign in to leave a comment.