Skip to content

Commit

Permalink
TwigExtensionParser replace recursive element visiting in favor of co…
Browse files Browse the repository at this point in the history
…ntrolflow
  • Loading branch information
Haehnchen committed May 3, 2023
1 parent f6be026 commit 0cf16fd
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.intellij.openapi.util.Key;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import com.intellij.psi.util.*;
import com.jetbrains.php.PhpIcons;
import com.jetbrains.php.PhpIndex;
Expand All @@ -19,10 +18,7 @@
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
Expand Down Expand Up @@ -82,14 +78,21 @@ public static Map<String, TwigExtension> getOperators(@NotNull Project project)
private static Map<String, TwigExtension> parseFilters(@NotNull Collection<PhpClass> phpClasses) {
Map<String, TwigExtension> extensions = new HashMap<>();

for(PhpClass phpClass : phpClasses) {
for (PhpClass phpClass : phpClasses) {
Method method = phpClass.findMethodByName("getFilters");
if(method != null) {
parseFilter(method, extensions);
if (method != null) {
final PhpClass containingClass = method.getContainingClass();
if (containingClass == null) {
continue;
}

for (NewExpression newExpression : PhpElementsUtil.collectNewExpressionsInsideControlFlow(method)) {
TwigFilterVisitor.visitNewExpression(newExpression, method, extensions, containingClass);
}
}
}

return extensions;
return Collections.unmodifiableMap(extensions);
}

@NotNull
Expand All @@ -99,11 +102,18 @@ private static Map<String, TwigExtension> parseFunctions(@NotNull Collection<Php
for(PhpClass phpClass : phpClasses) {
Method method = phpClass.findMethodByName("getFunctions");
if(method != null) {
parseFunctions(method, extensions);
final PhpClass containingClass = method.getContainingClass();
if(containingClass == null) {
continue;
}

for (NewExpression newExpression : PhpElementsUtil.collectNewExpressionsInsideControlFlow(method)) {
TwigFunctionVisitor.visitNewExpression(newExpression, method, extensions, containingClass);
}
}
}

return extensions;
return Collections.unmodifiableMap(extensions);
}

@NotNull
Expand All @@ -113,11 +123,13 @@ private static Map<String, TwigExtension> parseTests(@NotNull Collection<PhpClas
for(PhpClass phpClass : phpClasses) {
Method method = phpClass.findMethodByName("getTests");
if(method != null) {
method.acceptChildren(new TwigSimpleTestVisitor(extensions));
for (NewExpression newExpression : PhpElementsUtil.collectNewExpressionsInsideControlFlow(method)) {
TwigSimpleTestVisitor.visitNewExpression(newExpression, extensions);
}
}
}

return extensions;
return Collections.unmodifiableMap(extensions);
}

@NotNull
Expand All @@ -131,16 +143,7 @@ private static Map<String, TwigExtension> parseOperators(@NotNull Collection<Php
}
}

return extensions;
}

private static void parseFunctions(@NotNull Method method, @NotNull Map<String, TwigExtension> filters) {
final PhpClass containingClass = method.getContainingClass();
if(containingClass == null) {
return;
}

method.acceptChildren(new TwigFunctionVisitor(method, filters, containingClass));
return Collections.unmodifiableMap(extensions);
}

/**
Expand Down Expand Up @@ -210,18 +213,9 @@ private static String getCallableSignature(PsiElement psiElement, Method method)
return null;
}

private static void parseFilter(@NotNull Method method, @NotNull Map<String, TwigExtension> filters) {
final PhpClass containingClass = method.getContainingClass();
if(containingClass == null) {
return;
}

method.acceptChildren(new TwigFilterVisitor(method, filters, containingClass));
}

private static void parseOperators(@NotNull Method method, @NotNull Map<String, TwigExtension> filters) {
final PhpClass containingClass = method.getContainingClass();
if(containingClass == null) {
if (containingClass == null) {
return;
}

Expand All @@ -236,23 +230,20 @@ private static void parseOperators(@NotNull Method method, @NotNull Map<String,
);
*/

// getOperator return values, should one one by default
for (PhpReturn phpReturn : PsiTreeUtil.findChildrenOfType(method, PhpReturn.class)) {

// getOperator return values, should one by default
for (PsiElement phpReturnArgument : PhpElementsUtil.collectPhpReturnArgumentsInsideControlFlow(method)) {
// return element needs to be an array
PhpPsiElement firstPsiChild = phpReturn.getFirstPsiChild();
if(firstPsiChild instanceof ArrayCreationExpression) {

if (phpReturnArgument instanceof ArrayCreationExpression arrayCreationExpression) {
// twig core returns nested array with 2 items array creation elements
List<PsiElement> arrayValues = PhpPsiUtil.getChildren(
firstPsiChild,
arrayCreationExpression,
psiElement -> psiElement.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE
);

if(arrayValues.size() > 0) {
for (PsiElement psiElement : arrayValues) {

// double check for non crazy syntax
// double check for non-crazy syntax
if(!(psiElement instanceof PhpPsiElement)) {
continue;
}
Expand Down Expand Up @@ -319,33 +310,8 @@ public static PsiElement getExtensionTarget(@NotNull Project project, @NotNull T
return elements.iterator().next();
}

private static class TwigFilterVisitor extends PsiRecursiveElementWalkingVisitor {
@NotNull
private final Method method;

@NotNull
private final Map<String, TwigExtension> filters;

@NotNull
private final PhpClass containingClass;

TwigFilterVisitor(@NotNull Method method, @NotNull Map<String, TwigExtension> filters, @NotNull PhpClass containingClass) {
this.method = method;
this.filters = filters;
this.containingClass = containingClass;
}

@Override
public void visitElement(@NotNull PsiElement element) {
if(element instanceof NewExpression) {
this.visitNewExpression((NewExpression) element);
}

super.visitElement(element);
}

private void visitNewExpression(@NotNull NewExpression element) {

private static class TwigFilterVisitor {
private static void visitNewExpression(@NotNull NewExpression element, @NotNull Method method, @NotNull Map<String, TwigExtension> filters, @NotNull PhpClass containingClass) {
// new \Twig_SimpleFilter('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
if(PhpElementsUtil.isNewExpressionPhpClassWithInstance(element, "Twig_SimpleFilter", "Twig_Filter", "Twig\\TwigFilter")) {
PsiElement[] psiElement = element.getParameters();
Expand Down Expand Up @@ -446,31 +412,8 @@ static private Map<String, String> getOptions(@NotNull ArrayCreationExpression a
return options;
}

private static class TwigFunctionVisitor extends PsiRecursiveElementWalkingVisitor {
@NotNull
private final Method method;

@NotNull
private final Map<String, TwigExtension> filters;

@NotNull
private final PhpClass containingClass;

TwigFunctionVisitor(@NotNull Method method, @NotNull Map<String, TwigExtension> filters, @NotNull PhpClass containingClass) {
this.method = method;
this.filters = filters;
this.containingClass = containingClass;
}

@Override
public void visitElement(PsiElement element) {
if(element instanceof NewExpression) {
this.visitNewExpression((NewExpression) element);
}
super.visitElement(element);
}

private void visitNewExpression(@NotNull NewExpression element) {
private static class TwigFunctionVisitor {
private static void visitNewExpression(@NotNull NewExpression element, @NotNull Method method, @NotNull Map<String, TwigExtension> filters, @NotNull PhpClass containingClass) {

// new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
if(PhpElementsUtil.isNewExpressionPhpClassWithInstance(element, "Twig_SimpleFunction", "Twig_Function", "Twig\\TwigFunction")) {
Expand Down Expand Up @@ -556,24 +499,8 @@ private void visitNewExpression(@NotNull NewExpression element) {
}
}

private static class TwigSimpleTestVisitor extends PsiRecursiveElementWalkingVisitor {
@NotNull
private final Map<String, TwigExtension> filters;

TwigSimpleTestVisitor(@NotNull Map<String, TwigExtension> filters) {
this.filters = filters;
}

@Override
public void visitElement(PsiElement element) {
if(element instanceof NewExpression) {
this.visitNewExpression((NewExpression) element);
}
super.visitElement(element);
}

private void visitNewExpression(@NotNull NewExpression element) {

private static class TwigSimpleTestVisitor {
private static void visitNewExpression(@NotNull NewExpression element, @NotNull Map<String, TwigExtension> filters) {
// new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')),
if(PhpElementsUtil.isNewExpressionPhpClassWithInstance(element, "Twig_SimpleTest", "Twig_Test", "Twig\\TwigTest")) {
PsiElement[] psiElement = element.getParameters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import com.jetbrains.php.PhpClassHierarchyUtils;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
import com.jetbrains.php.codeInsight.PhpScopeHolder;
import com.jetbrains.php.codeInsight.controlFlow.PhpControlFlowUtil;
import com.jetbrains.php.codeInsight.controlFlow.PhpInstructionProcessor;
import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction;
import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpConstructorCallInstruction;
import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpReturnInstruction;
import com.jetbrains.php.completion.PhpLookupElement;
import com.jetbrains.php.lang.PhpLangUtil;
Expand Down Expand Up @@ -1894,6 +1896,39 @@ public static String getMethodReferenceStringValueParameter(@NotNull MethodRefer
return null;
}

@NotNull
public static Collection<NewExpression> collectNewExpressionsInsideControlFlow(@NotNull PhpScopeHolder phpScopeHolder) {
Collection<NewExpression> newExpressions = new ArrayList<>();

PhpControlFlowUtil.processFlow(phpScopeHolder.getControlFlow(), new PhpInstructionProcessor() {
@Override
public boolean processConstructorCallInstruction(PhpConstructorCallInstruction instruction) {
newExpressions.add(instruction.getNewExpression());
return super.processConstructorCallInstruction(instruction);
}
});

return newExpressions;
}

public static Collection<PsiElement> collectPhpReturnArgumentsInsideControlFlow(@NotNull PhpScopeHolder phpScopeHolder) {
Collection<PsiElement> elements = new ArrayList<>();

PhpControlFlowUtil.processFlow(phpScopeHolder.getControlFlow(), new PhpInstructionProcessor() {
@Override
public boolean processReturnInstruction(PhpReturnInstruction instruction) {
PsiElement argument = instruction.getArgument();
if (argument != null) {
elements.add(argument);
}

return super.processReturnInstruction(instruction);
}
});

return elements;
}

@NotNull
public static Collection<Function> getMethodReferenceMethods(@NotNull MethodReference methodReference) {
PhpIndex instance = PhpIndex.getInstance(methodReference.getProject());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public void testExtensionAreCollected() {
"#Fmax",
TwigExtensionParser.getFunctions(getProject()).get("class_php_callable_function_foobar").getSignature()
);

assertEquals(
"#Fmax",
TwigExtensionParser.getFunctions(getProject()).get("conditional_return").getSignature()
);
}

public void testExtensionAreCollectedForDeprecated() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ public function getTests()

public function getFunctions()
{
if ('webpack-encore-bundle' === 'foo') {
return new TwigFunction('conditional_return', 'max');
}

return [
new \Twig_SimpleFunction('max', 'max'),
'form_enctype' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\FormEnctypeNode'),
Expand Down

0 comments on commit 0cf16fd

Please sign in to comment.