Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.adrienbrault.idea.symfony2plugin.stubs.indexes;

import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiFile;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
Expand All @@ -16,24 +17,23 @@
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.stubs.dict.TemplateUsage;
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.externalizer.ObjectStreamDataExternalizer;
import fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil;
import kotlin.Triple;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.function.Consumer;

import static fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil.TemplateRenderPsiRecursiveElementWalkingVisitor.*;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class PhpTwigTemplateUsageStubIndex extends FileBasedIndexExtension<String, TemplateUsage> {

public static final ID<String, TemplateUsage> KEY = ID.create("fr.adrienbrault.idea.symfony2plugin.twig_php_usage");
private final KeyDescriptor<String> myKeyDescriptor = new EnumeratorStringDescriptor();
private static int MAX_FILE_BYTE_SIZE = 2097152;
private static ObjectStreamDataExternalizer<TemplateUsage> EXTERNALIZER = new ObjectStreamDataExternalizer<>();
private static final int MAX_FILE_BYTE_SIZE = 2097152;
private static final ObjectStreamDataExternalizer<TemplateUsage> EXTERNALIZER = new ObjectStreamDataExternalizer<>();

@NotNull
@Override
Expand Down Expand Up @@ -68,15 +68,16 @@ public DataIndexer<String, TemplateUsage, FileContent> getIndexer() {

items.get(templateName).add(StringUtils.stripStart(triple.getSecond().getFQN(), "\\"));
};
Set<String> methods = collectMethods(psiFile.getProject());

@NotNull NotNullLazyValue<Set<String>> methods = PhpMethodVariableResolveUtil.TemplateRenderVisitor.createLazyMethodNamesCollector(psiFile.getProject());
for (PhpNamedElement topLevelElement : ((PhpFile) psiFile).getTopLevelDefs().values()) {
if (topLevelElement instanceof PhpClass clazz) {
for (Method method : clazz.getOwnMethods()) {
processMethodAttributes(method, consumer);
PhpMethodVariableResolveUtil.TemplateRenderVisitor.processMethodAttributes(method, consumer);
PhpDocComment docComment = method.getDocComment();
if (docComment != null) {
PhpDocUtil.processTagElementsByName(docComment, null, docTag -> {
processDocTag(docTag, consumer);
PhpMethodVariableResolveUtil.TemplateRenderVisitor.processDocTag(docTag, consumer);
return true;
});
}
Expand All @@ -98,12 +99,12 @@ public DataIndexer<String, TemplateUsage, FileContent> getIndexer() {
};
}

private static void processMethodReferences(Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer, Set<String> methods, Function function) {
private static void processMethodReferences(@NotNull Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer, @NotNull NotNullLazyValue<Set<String>> methods, @NotNull Function function) {
PhpControlFlowUtil.processFlow(function.getControlFlow(), new PhpInstructionProcessor() {
@Override
public boolean processPhpCallInstruction(PhpCallInstruction instruction) {
if (instruction.getFunctionReference() instanceof MethodReference methodReference) {
processMethodReference(methodReference, methods, consumer);
PhpMethodVariableResolveUtil.TemplateRenderVisitor.processMethodReference(methodReference, methods, consumer);
}
return super.processPhpCallInstruction(instruction);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package fr.adrienbrault.idea.symfony2plugin.templating.util;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.php.codeInsight.controlFlow.PhpControlFlowUtil;
import com.jetbrains.php.codeInsight.controlFlow.PhpInstructionProcessor;
import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpCallInstruction;
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.parser.PhpElementTypes;
Expand Down Expand Up @@ -238,57 +241,32 @@ public static Map<String, PsiVariable> getTypesOnArrayHash(@NotNull ArrayCreatio
*
* As annotations are not in scope of the method itself
*/
public static void visitRenderTemplateFunctions(@NotNull Method method, @NotNull Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
TemplateRenderPsiRecursiveElementWalkingVisitor psiElementVisitor = new TemplateRenderPsiRecursiveElementWalkingVisitor(method, consumer);

PhpDocComment docComment = method.getDocComment();
public static void visitRenderTemplateFunctions(@NotNull Function function, @NotNull Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
PhpDocComment docComment = function.getDocComment();
for (PhpDocTag phpDocTag : PsiTreeUtil.getChildrenOfTypeAsList(docComment, PhpDocTag.class)) {
psiElementVisitor.visitPhpDocTag(phpDocTag);
}

for (PhpAttributesList phpAttributesList : PsiTreeUtil.getChildrenOfTypeAsList(method, PhpAttributesList.class)) {
psiElementVisitor.visitPhpAttribute(phpAttributesList);
TemplateRenderVisitor.processDocTag(phpDocTag, consumer);
}

method.accept(psiElementVisitor);
}

/**
* Visit all possible elements for render clements, scope shop be the class or a file itself
*/
public static void visitRenderTemplateFunctions(@NotNull PsiElement context, @NotNull Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
context.accept(new TemplateRenderPsiRecursiveElementWalkingVisitor(context, consumer));
}

public static class TemplateRenderPsiRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor {
private final PsiElement context;
private final Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer;
private Set<String> methods;

TemplateRenderPsiRecursiveElementWalkingVisitor(PsiElement context, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
this.context = context;
this.consumer = consumer;
methods = null;
}

@Override
public void visitElement(@NotNull PsiElement element) {
if(element instanceof MethodReference) {
visitMethodReference((MethodReference) element);
} else if(element instanceof PhpDocTag) {
visitPhpDocTag((PhpDocTag) element);
} else if(element instanceof PhpAttributesList) {
visitPhpAttribute((PhpAttributesList) element);
for (PhpAttributesList phpAttributesList : PsiTreeUtil.getChildrenOfTypeAsList(function, PhpAttributesList.class)) {
if (phpAttributesList.getParent() instanceof Method phpAttributeMethod) {
TemplateRenderVisitor.processMethodAttributes(phpAttributeMethod, consumer);
}
super.visitElement(element);
}

private void visitPhpAttribute(@NotNull PhpAttributesList phpAttributesList) {
if (phpAttributesList.getParent() instanceof Method method) {
processMethodAttributes(method, consumer);
NotNullLazyValue<Set<String>> lazyMethodNamesCollector = TemplateRenderVisitor.createLazyMethodNamesCollector(function.getProject());

PhpControlFlowUtil.processFlow(function.getControlFlow(), new PhpInstructionProcessor() {
@Override
public boolean processPhpCallInstruction(PhpCallInstruction instruction) {
if (instruction.getFunctionReference() instanceof MethodReference methodReference) {
TemplateRenderVisitor.processMethodReference(methodReference, lazyMethodNamesCollector, consumer);
}
return super.processPhpCallInstruction(instruction);
}
}
});
}

public static class TemplateRenderVisitor {
public static void processMethodAttributes(@NotNull Method method, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
Collection<@NotNull PhpAttribute> attributes = method.getAttributes(TwigUtil.TEMPLATE_ANNOTATION_CLASS);
for (PhpAttribute attribute : attributes) {
Expand All @@ -306,22 +284,8 @@ public static void processMethodAttributes(@NotNull Method method, Consumer<Trip
}
}

private void visitMethodReference(@NotNull MethodReference methodReference) {
String methodName = methodReference.getName();
if (methodName == null) {
return;
}

// init methods once per file
if(methods == null) {
methods = collectMethods(context.getProject());
}

processMethodReference(methodReference, methods, consumer);
}

@NotNull
public static Set<String> collectMethods(Project project) {
private static Set<String> collectMethodInner(@NotNull Project project) {
Set<String> methods = new HashSet<>();

PluginConfigurationExtension[] extensions = Symfony2ProjectComponent.PLUGIN_CONFIGURATION_EXTENSION.getExtensions();
Expand All @@ -336,13 +300,17 @@ public static Set<String> collectMethods(Project project) {
return methods;
}

public static void processMethodReference(@NotNull MethodReference methodReference, Set<String> methods, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
public static @NotNull NotNullLazyValue<Set<String>> createLazyMethodNamesCollector(@NotNull Project project) {
return NotNullLazyValue.lazy(() -> collectMethodInner(project));
}

public static void processMethodReference(@NotNull MethodReference methodReference, NotNullLazyValue<Set<String>> methods, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
String methodName = methodReference.getName();
if (methodName == null) {
return;
}

if(!methods.contains(methodName) && !methodName.toLowerCase().contains("render")) {
if(!methods.get().contains(methodName) && !methodName.toLowerCase().contains("render")) {
return;
}

Expand Down Expand Up @@ -432,12 +400,8 @@ private static void addStringLiteralScope(@NotNull MethodReference methodReferen
* "@Template("foobar.html.twig")"
* "@Template(template="foobar.html.twig")"
*/
private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) {
processDocTag(phpDocTag, consumer);
}

public static void processDocTag(@NotNull PhpDocTag phpDocTag, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
// "@var" and user non related tags dont need an action
// "@var" and user non-related tags don't need an action
if(AnnotationBackportUtil.NON_ANNOTATION_TAGS.contains(phpDocTag.getName())) {
return;
}
Expand Down