Skip to content

Commit

Permalink
Adds validation to check for inception in route Inertia page render m…
Browse files Browse the repository at this point in the history
…ethod.
  • Loading branch information
Alireza-Moh committed Feb 7, 2025
1 parent ba28835 commit 2eeb612
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package at.alirezamoh.whisperer_for_laravel.packages.inertia;

import at.alirezamoh.whisperer_for_laravel.support.utils.MethodUtils;
import at.alirezamoh.whisperer_for_laravel.support.utils.PhpClassUtils;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.lang.psi.elements.FunctionReference;
import com.jetbrains.php.lang.psi.elements.MethodReference;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.php.lang.psi.elements.impl.PhpClassImpl;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InertiaMethodValidator {
/**
* The names of the methods in the 'View' facade that can reference Blade files
*/
private static final Map<String, Integer> INERTIA_METHODS = new HashMap<>() {{
put("render", 0);
}};

/**
* The names of the methods in the 'View' facade that can reference Blade files
*/
private static final Map<String, Integer> ROUTE_INERTIA_METHODS = new HashMap<>() {{
put("inertia", 0);
}};

/**
* The FQN of the 'Route' facade
*/
private static final String INERTIA = "\\Inertia\\Inertia";

/**
* The FQN of the 'Route' facade
*/
private static final String ROUTE = "\\Illuminate\\Support\\Facades\\Route";

private InertiaMethodValidator() {}

/**
* Checks if a given PSI element is inside a valid Inertia method, Route method, or function.
*
* @param psiElement The PSI element to check.
* @return True if the element is inside a valid method or function, false otherwise.
*/
public static boolean isInsideCorrectMethod(@NotNull PsiElement psiElement) {
MethodReference method = MethodUtils.resolveMethodReference(psiElement, 10);
Project project = psiElement.getProject();

if (method != null) {
return isInertiaMethod(method, psiElement, project) || isRouteMethod(method, psiElement, project);
}

return isInertiaFunction(psiElement);
}

/**
* Checks if a given method reference matches an Inertia method.
*
* @param methodReference The method reference.
* @param position The PSI element position.
* @param project The current project.
* @return true or false
*/
private static boolean isInertiaMethod(MethodReference methodReference, PsiElement position, Project project) {
return isExpectedMethod(methodReference, position, project, INERTIA_METHODS, INERTIA);
}

/**
* Checks if a given method reference matches a Route method.
*
* @param methodReference The method reference.
* @param position The PSI element position.
* @param project The current project.
* @return true or false
*/
private static boolean isRouteMethod(MethodReference methodReference, PsiElement position, Project project) {
return isExpectedMethod(methodReference, position, project, ROUTE_INERTIA_METHODS, ROUTE);
}

/**
* Generalized logic for matching method references to a specific class and parameter criteria.
*
* @param methodReference The method reference.
* @param position The PSI element position.
* @param project The current project.
* @param methodMap Map of method names and their parameter indices.
* @param expectedClassFQN Fully qualified name of the expected class.
* @return true or false
*/
private static boolean isExpectedMethod(
MethodReference methodReference,
PsiElement position,
Project project,
Map<String, Integer> methodMap,
String expectedClassFQN
) {
Integer expectedParamIndex = methodMap.get(methodReference.getName());

if (expectedParamIndex == null) {
return false;
}

List<PhpClassImpl> resolvedClasses = getPhpClassesForMethod(methodReference, project);
PhpClass expectedClass = PhpClassUtils.getClassByFQN(project, expectedClassFQN);

return expectedClass != null
&& resolvedClasses.stream().anyMatch(clazz -> PhpClassUtils.isChildOf(clazz, expectedClass))
&& expectedParamIndex == MethodUtils.findParamIndex(position, false);
}

/**
* Checks if the given method reference is a view or route method
* @param position psi element
* @return true or false
*/
private static boolean isInertiaFunction(PsiElement position) {
FunctionReference functionReference = MethodUtils.resolveFunctionReference(position, 10);
if (functionReference == null) {
return false;
}

Integer expectedParamIndex = ROUTE_INERTIA_METHODS.get(functionReference.getName());

if (expectedParamIndex == null) {
return false;
}

return expectedParamIndex == MethodUtils.findParamIndex(position, false);
}

private static @NotNull List<PhpClassImpl> getPhpClassesForMethod(MethodReference methodReference, Project project) {
return MethodUtils.resolveMethodClasses(methodReference, project);
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,15 @@
package at.alirezamoh.whisperer_for_laravel.packages.inertia;

import at.alirezamoh.whisperer_for_laravel.support.utils.MethodUtils;
import at.alirezamoh.whisperer_for_laravel.support.utils.PhpClassUtils;
import at.alirezamoh.whisperer_for_laravel.support.utils.PluginUtils;
import at.alirezamoh.whisperer_for_laravel.support.utils.PsiElementUtils;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.util.ProcessingContext;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.elements.impl.PhpClassImpl;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.intellij.patterns.PlatformPatterns.psiElement;

public class InertiaReferenceContributor extends PsiReferenceContributor {
/**
* The names of the methods in the 'View' facade that can reference Blade files
*/
public static Map<String, Integer> INERTIA_METHODS = new HashMap<>() {{
put("render", 0);
}};

/**
* The names of the methods in the 'View' facade that can reference Blade files
*/
public static Map<String, Integer> ROUTE_INERTIA_METHODS = new HashMap<>() {{
put("inertia", 0);
}};

/**
* The FQN of the 'Route' facade
*/
private final String INERTIA = "\\Inertia\\Inertia";

/**
* The FQN of the 'Route' facade
*/
private final String ROUTE = "\\Illuminate\\Support\\Facades\\Route";

@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferenceRegistrar) {
psiReferenceRegistrar.registerReferenceProvider(
Expand All @@ -59,7 +26,7 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen
return PsiReference.EMPTY_ARRAY;
}

if (isInsideCorrectMethod(psiElement)) {
if (InertiaMethodValidator.isInsideCorrectMethod(psiElement)) {
return new PsiReference[]{new InertiaReference(
stringLiteralExpression,
new TextRange(
Expand All @@ -74,100 +41,4 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen
}
);
}

/**
* Checks if a given PSI element is inside a valid Inertia method, Route method, or function.
*
* @param psiElement The PSI element to check.
* @return True if the element is inside a valid method or function, false otherwise.
*/
private boolean isInsideCorrectMethod(@NotNull PsiElement psiElement) {
MethodReference method = MethodUtils.resolveMethodReference(psiElement, 10);
Project project = psiElement.getProject();

if (method != null) {
return isInertiaMethod(method, psiElement, project) || isRouteMethod(method, psiElement, project);
}

return isInertiaFunction(psiElement);
}

/**
* Checks if a given method reference matches an Inertia method.
*
* @param methodReference The method reference.
* @param position The PSI element position.
* @param project The current project.
* @return true or false
*/
private boolean isInertiaMethod(MethodReference methodReference, PsiElement position, Project project) {
return isExpectedMethod(methodReference, position, project, INERTIA_METHODS, INERTIA);
}

/**
* Checks if a given method reference matches a Route method.
*
* @param methodReference The method reference.
* @param position The PSI element position.
* @param project The current project.
* @return true or false
*/
private boolean isRouteMethod(MethodReference methodReference, PsiElement position, Project project) {
return isExpectedMethod(methodReference, position, project, ROUTE_INERTIA_METHODS, ROUTE);
}

/**
* Generalized logic for matching method references to a specific class and parameter criteria.
*
* @param methodReference The method reference.
* @param position The PSI element position.
* @param project The current project.
* @param methodMap Map of method names and their parameter indices.
* @param expectedClassFQN Fully qualified name of the expected class.
* @return true or false
*/
private boolean isExpectedMethod(
MethodReference methodReference,
PsiElement position,
Project project,
Map<String, Integer> methodMap,
String expectedClassFQN
) {
Integer expectedParamIndex = methodMap.get(methodReference.getName());

if (expectedParamIndex == null) {
return false;
}

List<PhpClassImpl> resolvedClasses = getPhpClassesForMethod(methodReference, project);
PhpClass expectedClass = PhpClassUtils.getClassByFQN(project, expectedClassFQN);

return expectedClass != null
&& resolvedClasses.stream().anyMatch(clazz -> PhpClassUtils.isChildOf(clazz, expectedClass))
&& expectedParamIndex == MethodUtils.findParamIndex(position, false);
}

private @NotNull List<PhpClassImpl> getPhpClassesForMethod(MethodReference methodReference, Project project) {
return MethodUtils.resolveMethodClasses(methodReference, project);
}

/**
* Checks if the given method reference is a view or route method
* @param position psi element
* @return true or false
*/
private boolean isInertiaFunction(PsiElement position) {
FunctionReference functionReference = MethodUtils.resolveFunctionReference(position, 10);
if (functionReference == null) {
return false;
}

Integer expectedParamIndex = ROUTE_INERTIA_METHODS.get(functionReference.getName());

if (expectedParamIndex == null) {
return false;
}

return expectedParamIndex == MethodUtils.findParamIndex(position, false);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package at.alirezamoh.whisperer_for_laravel.packages.inertia.annotator;

import at.alirezamoh.whisperer_for_laravel.packages.inertia.InertiaMethodValidator;
import at.alirezamoh.whisperer_for_laravel.packages.inertia.InertiaPageCollector;
import at.alirezamoh.whisperer_for_laravel.support.utils.StrUtils;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.lang.psi.elements.MethodReference;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;

/**
* Annotator that checks if an Inertia page exists
* <p>
Expand All @@ -39,20 +37,25 @@ public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder a
return;
}

if (psiElement instanceof StringLiteralExpression stringLiteralExpression) {
PsiElement parent = stringLiteralExpression.getParent().getParent();
if (!(psiElement instanceof StringLiteralExpression stringLiteralExpression)) {
return;
}

if (!InertiaMethodValidator.isInsideCorrectMethod(stringLiteralExpression)) {
return;
}

if (parent instanceof MethodReference methodReference && Objects.equals(methodReference.getName(), "render")) {
boolean exists = InertiaPageCollector.collectPages(project, false)
.stream()
.anyMatch(page -> page.getPath().equals(StrUtils.removeQuotes(stringLiteralExpression.getText())));
if (!exists) {
annotationHolder.newAnnotation(HighlightSeverity.WARNING, "Inertia page not found")
.range(stringLiteralExpression.getTextRange())
.withFix(new CreateInertiaPageIntention(stringLiteralExpression.getText()))
.create();
}
}
if (!doesPageExists(stringLiteralExpression, project)) {
annotationHolder.newAnnotation(HighlightSeverity.WARNING, "Inertia page not found")
.range(stringLiteralExpression.getTextRange())
.withFix(new CreateInertiaPageIntention(stringLiteralExpression.getText()))
.create();
}
}

private boolean doesPageExists(StringLiteralExpression stringLiteralExpression, Project project) {
return InertiaPageCollector.collectPages(project, false)
.stream()
.anyMatch(page -> page.getPath().equals(StrUtils.removeQuotes(stringLiteralExpression.getText())));
}
}

0 comments on commit 2eeb612

Please sign in to comment.