From 07e47ffb2656a0438e28a46919dd4c5ebe4e0dee Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 7 Sep 2019 12:59:05 +0200 Subject: [PATCH] remove custom related template search for controller and migrate to indexer visitor --- ...mplatesControllerRelatedGotoCollector.java | 83 +---- ...enderParameterGotoCompletionRegistrar.java | 8 +- .../util/PhpMethodVariableResolveUtil.java | 308 ++++++++++-------- 3 files changed, 181 insertions(+), 218 deletions(-) diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/controller/TemplatesControllerRelatedGotoCollector.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/controller/TemplatesControllerRelatedGotoCollector.java index cb2c1eb32..0726d083b 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/controller/TemplatesControllerRelatedGotoCollector.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/navigation/controller/TemplatesControllerRelatedGotoCollector.java @@ -1,100 +1,33 @@ package fr.adrienbrault.idea.symfony2plugin.navigation.controller; -import com.intellij.openapi.util.Pair; -import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; -import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; -import com.jetbrains.php.lang.psi.elements.Method; -import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; -import fr.adrienbrault.idea.symfony2plugin.config.SymfonyPhpReferenceContributor; import fr.adrienbrault.idea.symfony2plugin.dic.RelatedPopupGotoLineMarker; import fr.adrienbrault.idea.symfony2plugin.extension.ControllerActionGotoRelatedCollector; import fr.adrienbrault.idea.symfony2plugin.extension.ControllerActionGotoRelatedCollectorParameter; +import fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; -import fr.adrienbrault.idea.symfony2plugin.util.AnnotationBackportUtil; -import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; -import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import icons.TwigIcons; -import org.jetbrains.annotations.NotNull; -import java.util.*; -import java.util.function.Consumer; +import java.util.HashSet; +import java.util.Set; /** * @author Daniel Espendiller */ public class TemplatesControllerRelatedGotoCollector implements ControllerActionGotoRelatedCollector { - @Override public void collectGotoRelatedItems(ControllerActionGotoRelatedCollectorParameter parameter) { Set uniqueTemplates = new HashSet<>(); - Method method = parameter.getMethod(); - - visitMethodTemplateNames(method, pair -> { - String templateName = pair.getFirst(); - - if(!uniqueTemplates.contains(templateName)) { - uniqueTemplates.add(templateName); - - for (PsiElement psiElement : pair.getSecond()) { - parameter.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem( - psiElement, - TwigUtil.getFoldingTemplateNameOrCurrent(templateName)).withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER) - ); - } - } + PhpMethodVariableResolveUtil.visitRenderTemplateFunctions(parameter.getMethod(), triple -> { + uniqueTemplates.add(triple.getFirst()); }); - for(PsiElement psiElement: parameter.getParameterLists()) { - MethodMatcher.MethodMatchParameter matchedSignature = MethodMatcher.getMatchedSignatureWithDepth(psiElement, SymfonyPhpReferenceContributor.TEMPLATE_SIGNATURES); - if (matchedSignature != null) { - String resolveString = PhpElementsUtil.getStringValue(psiElement); - if(resolveString != null && !uniqueTemplates.contains(resolveString)) { - uniqueTemplates.add(resolveString); - for(PsiFile templateTarget: TwigUtil.getTemplatePsiElements(parameter.getProject(), resolveString)) { - parameter.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(templateTarget, resolveString).withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER)); - } - } + for (String uniqueTemplate : uniqueTemplates) { + for(PsiFile templateTarget: TwigUtil.getTemplatePsiElements(parameter.getProject(), uniqueTemplate)) { + parameter.add(new RelatedPopupGotoLineMarker.PopupGotoRelatedItem(templateTarget, uniqueTemplate).withIcon(TwigIcons.TwigFileIcon, Symfony2Icons.TWIG_LINE_MARKER)); } } } - - /** - * Visit every possible template on given method: eg Annotations @Template() - */ - public static void visitMethodTemplateNames(@NotNull Method method, @NotNull Consumer>> consumer) { - // on @Template annotation - PhpDocComment phpDocComment = method.getDocComment(); - if(phpDocComment != null) { - Collection phpDocTags = AnnotationBackportUtil.filterValidDocTags(PsiTreeUtil.findChildrenOfType(phpDocComment, PhpDocTag.class)); - if(phpDocTags.size() > 0) { - // cache use map for this phpDocComment - Map importMap = AnnotationBackportUtil.getUseImportMap(phpDocComment); - if(importMap.size() > 0) { - for(PhpDocTag phpDocTag: phpDocTags) { - // resolve annotation and check for template - PhpClass phpClass = AnnotationBackportUtil.getAnnotationReference(phpDocTag, importMap); - if(phpClass != null && PhpElementsUtil.isEqualClassName(phpClass, TwigUtil.TEMPLATE_ANNOTATION_CLASS)) { - Pair> templateAnnotationFiles = TwigUtil.getTemplateAnnotationFiles(phpDocTag); - if(templateAnnotationFiles != null) { - consumer.accept(Pair.create(templateAnnotationFiles.getFirst(), templateAnnotationFiles.getSecond())); - } - } - } - } - } - } - - // on method name - for (String templateName : TwigUtil.getControllerMethodShortcut(method)) { - consumer.accept(Pair.create( - templateName, - new HashSet<>(TwigUtil.getTemplatePsiElements(method.getProject(), templateName)) - )); - } - } } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/RenderParameterGotoCompletionRegistrar.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/RenderParameterGotoCompletionRegistrar.java index e9df43570..85d22908f 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/RenderParameterGotoCompletionRegistrar.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/RenderParameterGotoCompletionRegistrar.java @@ -12,7 +12,7 @@ import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar; import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter; import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil; -import fr.adrienbrault.idea.symfony2plugin.navigation.controller.TemplatesControllerRelatedGotoCollector; +import fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import icons.TwigIcons; @@ -164,9 +164,9 @@ public static Collection getTemplatesForScope(@NotNull PsiElement psiEle // } Method method = PsiTreeUtil.getParentOfType(parentArrayCreation, Method.class); if(method != null) { - TemplatesControllerRelatedGotoCollector.visitMethodTemplateNames(method, pair -> templates.add( - TwigUtil.normalizeTemplateName(pair.getFirst()) - )); + PhpMethodVariableResolveUtil.visitRenderTemplateFunctions(method, triple -> + templates.add(TwigUtil.normalizeTemplateName(triple.getFirst())) + ); } } else { // foobar('foo.html.twig', ['']) diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java index 7aa82f67d..3badb7ed1 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java @@ -3,6 +3,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiRecursiveElementWalkingVisitor; import com.intellij.psi.util.PsiTreeUtil; +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; import com.jetbrains.php.lang.psi.elements.*; @@ -239,68 +240,86 @@ public static Map getTypesOnArrayHash(ArrayCreationExpressi return collectedTypes; } + /** + * Visit method scope for template renders, also via annotation of the method itself + * + * As annotations are not in scope of the method itself + */ + public static void visitRenderTemplateFunctions(@NotNull Method method, @NotNull Consumer> consumer) { + TemplateRenderPsiRecursiveElementWalkingVisitor psiElementVisitor = new TemplateRenderPsiRecursiveElementWalkingVisitor(method, consumer); + + PhpDocComment docComment = method.getDocComment(); + for (PhpDocTag phpDocTag : PsiTreeUtil.getChildrenOfTypeAsList(docComment, PhpDocTag.class)) { + psiElementVisitor.visitPhpDocTag(phpDocTag); + } + + 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> consumer) { - context.accept(new PsiRecursiveElementWalkingVisitor() { - private Set methods = null; - - @Override - public void visitElement(PsiElement element) { - if(element instanceof MethodReference) { - visitMethodReference((MethodReference) element); - } else if(element instanceof PhpDocTag) { - visitPhpDocTag((PhpDocTag) element); - } - super.visitElement(element); - } + context.accept(new TemplateRenderPsiRecursiveElementWalkingVisitor(context, consumer)); + } + + private static class TemplateRenderPsiRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor { + private final PsiElement context; + private final Consumer> consumer; + private Set methods; - private void visitMethodReference(@NotNull MethodReference methodReference) { - String methodName = methodReference.getName(); + TemplateRenderPsiRecursiveElementWalkingVisitor(PsiElement context, Consumer> consumer) { + this.context = context; + this.consumer = consumer; + methods = null; + } - // init methods once per file - if(methods == null) { - methods = new HashSet<>(); - methods.addAll(RENDER_METHODS); + @Override + public void visitElement(PsiElement element) { + if(element instanceof MethodReference) { + visitMethodReference((MethodReference) element); + } else if(element instanceof PhpDocTag) { + visitPhpDocTag((PhpDocTag) element); + } + super.visitElement(element); + } - PluginConfigurationExtension[] extensions = Symfony2ProjectComponent.PLUGIN_CONFIGURATION_EXTENSION.getExtensions(); - if(extensions.length > 0) { - PluginConfigurationExtensionParameter pluginConfiguration = new PluginConfigurationExtensionParameter(context.getProject()); - for (PluginConfigurationExtension extension : extensions) { - extension.invokePluginConfiguration(pluginConfiguration); - } + private void visitMethodReference(@NotNull MethodReference methodReference) { + String methodName = methodReference.getName(); - methods.addAll(pluginConfiguration.getTemplateUsageMethod()); + // init methods once per file + if(methods == null) { + methods = new HashSet<>(); + methods.addAll(RENDER_METHODS); + + PluginConfigurationExtension[] extensions = Symfony2ProjectComponent.PLUGIN_CONFIGURATION_EXTENSION.getExtensions(); + if(extensions.length > 0) { + PluginConfigurationExtensionParameter pluginConfiguration = new PluginConfigurationExtensionParameter(context.getProject()); + for (PluginConfigurationExtension extension : extensions) { + extension.invokePluginConfiguration(pluginConfiguration); } - } - if(!methods.contains(methodName)) { - return; + methods.addAll(pluginConfiguration.getTemplateUsageMethod()); } + } - PsiElement[] parameters = methodReference.getParameters(); - if(parameters.length == 0) { - return; - } + if(!methods.contains(methodName)) { + return; + } - if (parameters[0] instanceof StringLiteralExpression) { - resolveString(methodReference, parameters[0]); - } else if(parameters[0] instanceof TernaryExpression) { - // render(true === true ? 'foo.twig.html' : 'foobar.twig.html') - for (PhpPsiElement phpPsiElement : new PhpPsiElement[]{((TernaryExpression) parameters[0]).getTrueVariant(), ((TernaryExpression) parameters[0]).getFalseVariant()}) { - if (phpPsiElement == null) { - continue; - } + PsiElement[] parameters = methodReference.getParameters(); + if(parameters.length == 0) { + return; + } - if (phpPsiElement instanceof StringLiteralExpression) { - resolveString(methodReference, phpPsiElement); - } else if(phpPsiElement instanceof PhpReference) { - resolvePhpReference(methodReference, phpPsiElement); - } + if (parameters[0] instanceof StringLiteralExpression) { + resolveString(methodReference, parameters[0]); + } else if(parameters[0] instanceof TernaryExpression) { + // render(true === true ? 'foo.twig.html' : 'foobar.twig.html') + for (PhpPsiElement phpPsiElement : new PhpPsiElement[]{((TernaryExpression) parameters[0]).getTrueVariant(), ((TernaryExpression) parameters[0]).getFalseVariant()}) { + if (phpPsiElement == null) { + continue; } - } else if(parameters[0] instanceof PhpReference) { - resolvePhpReference(methodReference, parameters[0]); - } else if(parameters[0] instanceof BinaryExpression) { - // render($foo ?? 'foo.twig.html') - PsiElement phpPsiElement = ((BinaryExpression) parameters[0]).getRightOperand(); if (phpPsiElement instanceof StringLiteralExpression) { resolveString(methodReference, phpPsiElement); @@ -308,118 +327,129 @@ private void visitMethodReference(@NotNull MethodReference methodReference) { resolvePhpReference(methodReference, phpPsiElement); } } - } - - private void resolveString(@NotNull MethodReference methodReference, PsiElement parameter) { - // foo('foo.html.twig') - String contents = ((StringLiteralExpression) parameter).getContents(); - if (StringUtils.isBlank(contents) || !contents.endsWith(".twig")) { - return; + } else if(parameters[0] instanceof PhpReference) { + resolvePhpReference(methodReference, parameters[0]); + } else if(parameters[0] instanceof BinaryExpression) { + // render($foo ?? 'foo.twig.html') + PsiElement phpPsiElement = ((BinaryExpression) parameters[0]).getRightOperand(); + + if (phpPsiElement instanceof StringLiteralExpression) { + resolveString(methodReference, phpPsiElement); + } else if(phpPsiElement instanceof PhpReference) { + resolvePhpReference(methodReference, phpPsiElement); } + } + } - Function parentOfType = PsiTreeUtil.getParentOfType(methodReference, Function.class); - if(parentOfType == null) { - return; - } + private void resolveString(@NotNull MethodReference methodReference, PsiElement parameter) { + // foo('foo.html.twig') + String contents = ((StringLiteralExpression) parameter).getContents(); + if (StringUtils.isBlank(contents) || !contents.endsWith(".twig")) { + return; + } - addTemplateWithScope(contents, parentOfType, methodReference); + Function parentOfType = PsiTreeUtil.getParentOfType(methodReference, Function.class); + if(parentOfType == null) { + return; } - private void resolvePhpReference(@NotNull MethodReference methodReference, PsiElement parameter) { - for (PhpNamedElement phpNamedElement : ((PhpReference) parameter).resolveLocal()) { - // foo(self::foo) - // foo($this->foo) - if (phpNamedElement instanceof Field) { - PsiElement defaultValue = ((Field) phpNamedElement).getDefaultValue(); - if (defaultValue instanceof StringLiteralExpression) { - addStringLiteralScope(methodReference, (StringLiteralExpression) defaultValue); - } + addTemplateWithScope(contents, parentOfType, methodReference); + } + + private void resolvePhpReference(@NotNull MethodReference methodReference, PsiElement parameter) { + for (PhpNamedElement phpNamedElement : ((PhpReference) parameter).resolveLocal()) { + // foo(self::foo) + // foo($this->foo) + if (phpNamedElement instanceof Field) { + PsiElement defaultValue = ((Field) phpNamedElement).getDefaultValue(); + if (defaultValue instanceof StringLiteralExpression) { + addStringLiteralScope(methodReference, (StringLiteralExpression) defaultValue); } + } - // foo($var) => $var = 'test.html.twig' - if (phpNamedElement instanceof Variable) { - PsiElement assignmentExpression = phpNamedElement.getParent(); - if (assignmentExpression instanceof AssignmentExpression) { - PhpPsiElement value = ((AssignmentExpression) assignmentExpression).getValue(); - if (value instanceof StringLiteralExpression) { - addStringLiteralScope(methodReference, (StringLiteralExpression) value); - } + // foo($var) => $var = 'test.html.twig' + if (phpNamedElement instanceof Variable) { + PsiElement assignmentExpression = phpNamedElement.getParent(); + if (assignmentExpression instanceof AssignmentExpression) { + PhpPsiElement value = ((AssignmentExpression) assignmentExpression).getValue(); + if (value instanceof StringLiteralExpression) { + addStringLiteralScope(methodReference, (StringLiteralExpression) value); } } } } + } - private void addStringLiteralScope(@NotNull MethodReference methodReference, @NotNull StringLiteralExpression defaultValue) { - String contents = defaultValue.getContents(); - if (StringUtils.isBlank(contents) || !contents.endsWith(".twig")) { - return; - } - - Function parentOfType = PsiTreeUtil.getParentOfType(methodReference, Function.class); - if (parentOfType == null) { - return; - } + private void addStringLiteralScope(@NotNull MethodReference methodReference, @NotNull StringLiteralExpression defaultValue) { + String contents = defaultValue.getContents(); + if (StringUtils.isBlank(contents) || !contents.endsWith(".twig")) { + return; + } - addTemplateWithScope(contents, parentOfType, methodReference); + Function parentOfType = PsiTreeUtil.getParentOfType(methodReference, Function.class); + if (parentOfType == null) { + return; } - /** - * "@Template("foobar.html.twig")" - * "@Template(template="foobar.html.twig")" - */ - private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) { - // "@var" and user non related tags dont need an action - if(AnnotationBackportUtil.NON_ANNOTATION_TAGS.contains(phpDocTag.getName())) { - return; - } + addTemplateWithScope(contents, parentOfType, methodReference); + } - // init scope imports - Map fileImports = AnnotationBackportUtil.getUseImportMap(phpDocTag); - if(fileImports.size() == 0) { - return; - } + /** + * "@Template("foobar.html.twig")" + * "@Template(template="foobar.html.twig")" + */ + private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) { + // "@var" and user non related tags dont need an action + if(AnnotationBackportUtil.NON_ANNOTATION_TAGS.contains(phpDocTag.getName())) { + return; + } - String annotationFqnName = AnnotationBackportUtil.getClassNameReference(phpDocTag, fileImports); - if(!StringUtils.stripStart(TwigUtil.TEMPLATE_ANNOTATION_CLASS, "\\").equals(StringUtils.stripStart(annotationFqnName, "\\"))) { - return; - } + // init scope imports + Map fileImports = AnnotationBackportUtil.getUseImportMap(phpDocTag); + if(fileImports.size() == 0) { + return; + } + + String annotationFqnName = AnnotationBackportUtil.getClassNameReference(phpDocTag, fileImports); + if(!StringUtils.stripStart(TwigUtil.TEMPLATE_ANNOTATION_CLASS, "\\").equals(StringUtils.stripStart(annotationFqnName, "\\"))) { + return; + } - String template = AnnotationUtil.getPropertyValueOrDefault(phpDocTag, "template"); - if (template == null) { - // see \Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser - // App\Controller\MyNiceController::myAction => my_nice/my.html.twig - Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag); - if(methodScope != null) { - PhpClass phpClass = methodScope.getContainingClass(); - if (phpClass != null) { - // App\Controller\ "MyNice" Controller - Matcher matcher = Pattern.compile("Controller\\\\(.+)Controller$", Pattern.MULTILINE).matcher(StringUtils.stripStart(phpClass.getFQN(), "\\")); - if(matcher.find()){ - String group = underscore(matcher.group(1).replace("\\", "/")); - String name = methodScope.getName(); - - // __invoke is using controller as template name - if (name.equals("__invoke")) { - addTemplateWithScope(group + ".html.twig", methodScope, null); - } else { - String action = name.endsWith("Action") ? name.substring(0, name.length() - "Action".length()) : name; - addTemplateWithScope(group + "/" + underscore(action) + ".html.twig", methodScope, null); - } + String template = AnnotationUtil.getPropertyValueOrDefault(phpDocTag, "template"); + if (template == null) { + // see \Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser + // App\Controller\MyNiceController::myAction => my_nice/my.html.twig + Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag); + if(methodScope != null) { + PhpClass phpClass = methodScope.getContainingClass(); + if (phpClass != null) { + // App\Controller\ "MyNice" Controller + Matcher matcher = Pattern.compile("Controller\\\\(.+)Controller$", Pattern.MULTILINE).matcher(StringUtils.stripStart(phpClass.getFQN(), "\\")); + if(matcher.find()){ + String group = underscore(matcher.group(1).replace("\\", "/")); + String name = methodScope.getName(); + + // __invoke is using controller as template name + if (name.equals("__invoke")) { + addTemplateWithScope(group + ".html.twig", methodScope, null); + } else { + String action = name.endsWith("Action") ? name.substring(0, name.length() - "Action".length()) : name; + addTemplateWithScope(group + "/" + underscore(action) + ".html.twig", methodScope, null); } } } - } else if(template.endsWith(".twig")) { - Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag); - if(methodScope != null) { - addTemplateWithScope(template, methodScope, null); - } + } + } else if(template.endsWith(".twig")) { + Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag); + if(methodScope != null) { + addTemplateWithScope(template, methodScope, null); } } + } - private void addTemplateWithScope(@NotNull String contents, @NotNull PhpNamedElement scope, @Nullable FunctionReference functionReference) { - String s = TwigUtil.normalizeTemplateName(contents); - consumer.accept(new Triple<>(s, scope, functionReference)); - } - }); + private void addTemplateWithScope(@NotNull String contents, @NotNull PhpNamedElement scope, @Nullable FunctionReference functionReference) { + String s = TwigUtil.normalizeTemplateName(contents); + consumer.accept(new Triple<>(s, scope, functionReference)); + } } }