How can I implement "Go to Declaration" functionality in a PHPStorm plugin?

Answered

Heeeeyyy :)

I am developing a PHPStorm plugin for Laravel that provides autocompletion for form request fields. I've successfully implemented the autocompletion, but I am now trying to add "Go to Declaration" functionality, so when a user selects a form request field, they can navigate directly to its declaration in the rules method of the FormRequest class.

I need help implementing "Go to Declaration" for the completion elements. Specifically, when a user selects a field like name or email from the completion suggestions, I want to navigate them directly to the relevant line in the rules method where this field is defined.


What steps should I take to achieve this?
 

Additionally, I would like to know if there is a better way to retrieve the PhpClass of a VariableImpl than using variable.getGlobalType().toString()? Are there more efficient or reliable methods to achieve this?

 

 

CompletionContributor class:


package at.test.package.validation;

import at.test.package.BaseCompletion;
import at.test.package.support.LaravelVisionIcon;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.psi.elements.Method;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.php.lang.psi.elements.impl.*;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;

public class FormRequestFieldCompletionContributor extends BaseCompletion {
    public FormRequestFieldCompletionContributor() {
        extend(CompletionType.BASIC, PlatformPatterns.psiElement().withElementType(PhpTokenTypes.IDENTIFIER), new CompletionProvider<>() {
            @Override
            protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
                PsiElement psiElement = parameters.getPosition();

                VariableImpl variable = PsiTreeUtil.getPrevSiblingOfType(psiElement, VariableImpl.class);
                if (variable != null) {

                    PhpIndex phpIndex = PhpIndex.getInstance(psiElement.getProject());
                    Collection<PhpClass> formRequestClass = phpIndex.getClassesByFQN(variable.getGlobalType().toString());

                    for (PhpClass phpClass : formRequestClass) {
                        Method rulesMethod = phpClass.findMethodByName("rules");

                        if (rulesMethod != null) {
                            processRulesMethod(rulesMethod, result);
                        }
                    }
                }
            }
        });
    }

    private void processRulesMethod(@NotNull Method rulesMethod, @NotNull CompletionResultSet result) {
        ArrayCreationExpressionImpl arrayCreationExpression = PsiTreeUtil.findChildOfType(rulesMethod, ArrayCreationExpressionImpl.class);

        if (arrayCreationExpression != null) {
            ArrayHashElementImpl[] arrayHashElements = PsiTreeUtil.getChildrenOfType(arrayCreationExpression, ArrayHashElementImpl.class);

            if (arrayHashElements != null) {
                for (ArrayHashElementImpl arrayHashElement : arrayHashElements) {
                    addCompletionFromArrayHashElement(arrayHashElement, result);
                }
            }
        }
    }

    private void addCompletionFromArrayHashElement(@NotNull ArrayHashElementImpl arrayHashElement, @NotNull CompletionResultSet result) {
        PsiElement keyElement = arrayHashElement.getKey();
        if (keyElement != null) {
            String keyText = keyElement.getText().replace("'", "");
            LookupElementBuilder lookupElement = LookupElementBuilder.create(keyText)
                    .withLookupString(keyText)
                    .withPresentableText(keyText)
                    .withIcon(LaravelVisionIcon.LARAVEL_ICON)
                    .withPsiElement(keyElement);
            result.addElement(PrioritizedLookupElement.withPriority(lookupElement, 100));
        }
    }
}

 

Here’s an example of how the rules method might look like in a typical Laravel form request class:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => 'required',
            'password' => 'required'
        ];
    }
}

 

Users will access the form request field in their code like this:

public function login(LoginRequest $request)
{
    $request->email; //access the variable/key

    return view('welcome');
}



Thx :)

0
1 comment

You need to provide PSI References https://plugins.jetbrains.com/docs/intellij/references-and-resolve.html (which can also act as completion item providers). 

0

Please sign in to leave a comment.