Skip to content

Commit 69868ca

Browse files
committed
[performance] speed up PhpTwigTemplateUsageStubIndex: replace recursive visitor with linear scans
1 parent 36440af commit 69868ca

File tree

2 files changed

+100
-42
lines changed

2 files changed

+100
-42
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/PhpTwigTemplateUsageStubIndex.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,25 @@
55
import com.intellij.util.io.DataExternalizer;
66
import com.intellij.util.io.EnumeratorStringDescriptor;
77
import com.intellij.util.io.KeyDescriptor;
8+
import com.jetbrains.php.codeInsight.controlFlow.PhpControlFlowUtil;
9+
import com.jetbrains.php.codeInsight.controlFlow.PhpInstructionProcessor;
10+
import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpCallInstruction;
11+
import com.jetbrains.php.lang.documentation.phpdoc.PhpDocUtil;
12+
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
813
import com.jetbrains.php.lang.psi.PhpFile;
14+
import com.jetbrains.php.lang.psi.elements.*;
915
import com.jetbrains.php.lang.psi.stubs.indexes.PhpConstantNameIndex;
1016
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
1117
import fr.adrienbrault.idea.symfony2plugin.stubs.dict.TemplateUsage;
1218
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.externalizer.ObjectStreamDataExternalizer;
13-
import fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil;
19+
import kotlin.Triple;
1420
import org.apache.commons.lang.StringUtils;
1521
import org.jetbrains.annotations.NotNull;
1622

1723
import java.util.*;
24+
import java.util.function.Consumer;
25+
26+
import static fr.adrienbrault.idea.symfony2plugin.templating.util.PhpMethodVariableResolveUtil.TemplateRenderPsiRecursiveElementWalkingVisitor.*;
1827

1928
/**
2029
* @author Daniel Espendiller <daniel@espendiller.net>
@@ -46,8 +55,7 @@ public DataIndexer<String, TemplateUsage, FileContent> getIndexer() {
4655
}
4756

4857
Map<String, Set<String>> items = new HashMap<>();
49-
50-
PhpMethodVariableResolveUtil.visitRenderTemplateFunctions(psiFile, triple -> {
58+
Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer = triple -> {
5159
String templateName = triple.getFirst();
5260

5361
if (templateName.length() > 255) {
@@ -59,7 +67,26 @@ public DataIndexer<String, TemplateUsage, FileContent> getIndexer() {
5967
}
6068

6169
items.get(templateName).add(StringUtils.stripStart(triple.getSecond().getFQN(), "\\"));
62-
});
70+
};
71+
Set<String> methods = collectMethods(psiFile.getProject());
72+
for (PhpNamedElement topLevelElement : ((PhpFile) psiFile).getTopLevelDefs().values()) {
73+
if (topLevelElement instanceof PhpClass clazz) {
74+
for (Method method : clazz.getOwnMethods()) {
75+
processMethodAttributes(method, consumer);
76+
PhpDocComment docComment = method.getDocComment();
77+
if (docComment != null) {
78+
PhpDocUtil.processTagElementsByName(docComment, null, docTag -> {
79+
processDocTag(docTag, consumer);
80+
return true;
81+
});
82+
}
83+
processMethodReferences(consumer, methods, method);
84+
}
85+
}
86+
if (topLevelElement instanceof Function function) {
87+
processMethodReferences(consumer, methods, function);
88+
}
89+
}
6390

6491
Map<String, TemplateUsage> map = new HashMap<>();
6592

@@ -71,6 +98,18 @@ public DataIndexer<String, TemplateUsage, FileContent> getIndexer() {
7198
};
7299
}
73100

101+
private static void processMethodReferences(Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer, Set<String> methods, Function function) {
102+
PhpControlFlowUtil.processFlow(function.getControlFlow(), new PhpInstructionProcessor() {
103+
@Override
104+
public boolean processPhpCallInstruction(PhpCallInstruction instruction) {
105+
if (instruction.getFunctionReference() instanceof MethodReference methodReference) {
106+
processMethodReference(methodReference, methods, consumer);
107+
}
108+
return super.processPhpCallInstruction(instruction);
109+
}
110+
});
111+
}
112+
74113
@NotNull
75114
@Override
76115
public KeyDescriptor<String> getKeyDescriptor() {

src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/PhpMethodVariableResolveUtil.java

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fr.adrienbrault.idea.symfony2plugin.templating.util;
22

3+
import com.intellij.openapi.project.Project;
34
import com.intellij.openapi.util.Pair;
45
import com.intellij.psi.PsiElement;
56
import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
@@ -259,7 +260,7 @@ public static void visitRenderTemplateFunctions(@NotNull PsiElement context, @No
259260
context.accept(new TemplateRenderPsiRecursiveElementWalkingVisitor(context, consumer));
260261
}
261262

262-
private static class TemplateRenderPsiRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor {
263+
public static class TemplateRenderPsiRecursiveElementWalkingVisitor extends PsiRecursiveElementWalkingVisitor {
263264
private final PsiElement context;
264265
private final Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer;
265266
private Set<String> methods;
@@ -283,24 +284,23 @@ public void visitElement(@NotNull PsiElement element) {
283284
}
284285

285286
private void visitPhpAttribute(@NotNull PhpAttributesList phpAttributesList) {
286-
Collection<@NotNull PhpAttribute> attributes = phpAttributesList.getAttributes(TwigUtil.TEMPLATE_ANNOTATION_CLASS);
287+
if (phpAttributesList.getParent() instanceof Method method) {
288+
processMethodAttributes(method, consumer);
289+
}
290+
}
291+
292+
public static void processMethodAttributes(@NotNull Method method, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
293+
Collection<@NotNull PhpAttribute> attributes = method.getAttributes(TwigUtil.TEMPLATE_ANNOTATION_CLASS);
287294
for (PhpAttribute attribute : attributes) {
288295
if (attribute.getArguments().isEmpty()) {
289296
// #[@Template()]
290-
PsiElement parent = phpAttributesList.getParent();
291-
if (parent instanceof Method) {
292-
visitMethodForGuessing((Method) parent);
293-
}
297+
visitMethodForGuessing(method, consumer);
294298
} else {
295299
// [@Template("foobar.html.twig")]
296300
// #[@Template(template: "foobar.html.twig")]
297301
String template = PhpPsiAttributesUtil.getAttributeValueByNameAsStringWithDefaultParameterFallback(attribute, "template");
298302
if (StringUtils.isNotBlank(template)) {
299-
PsiElement parent = phpAttributesList.getParent();
300-
if (parent instanceof Method) {
301-
addTemplateWithScope(template, (Method) parent, null);
302-
303-
}
303+
addTemplateWithScope(template, method, null, consumer);
304304
}
305305
}
306306
}
@@ -314,17 +314,32 @@ private void visitMethodReference(@NotNull MethodReference methodReference) {
314314

315315
// init methods once per file
316316
if(methods == null) {
317-
methods = new HashSet<>();
317+
methods = collectMethods(context.getProject());
318+
}
318319

319-
PluginConfigurationExtension[] extensions = Symfony2ProjectComponent.PLUGIN_CONFIGURATION_EXTENSION.getExtensions();
320-
if(extensions.length > 0) {
321-
PluginConfigurationExtensionParameter pluginConfiguration = new PluginConfigurationExtensionParameter(context.getProject());
322-
for (PluginConfigurationExtension extension : extensions) {
323-
extension.invokePluginConfiguration(pluginConfiguration);
324-
}
320+
processMethodReference(methodReference, methods, consumer);
321+
}
322+
323+
@NotNull
324+
public static Set<String> collectMethods(Project project) {
325+
Set<String> methods = new HashSet<>();
325326

326-
methods.addAll(pluginConfiguration.getTemplateUsageMethod());
327+
PluginConfigurationExtension[] extensions = Symfony2ProjectComponent.PLUGIN_CONFIGURATION_EXTENSION.getExtensions();
328+
if(extensions.length > 0) {
329+
PluginConfigurationExtensionParameter pluginConfiguration = new PluginConfigurationExtensionParameter(project);
330+
for (PluginConfigurationExtension extension : extensions) {
331+
extension.invokePluginConfiguration(pluginConfiguration);
327332
}
333+
334+
methods.addAll(pluginConfiguration.getTemplateUsageMethod());
335+
}
336+
return methods;
337+
}
338+
339+
public static void processMethodReference(@NotNull MethodReference methodReference, Set<String> methods, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
340+
String methodName = methodReference.getName();
341+
if (methodName == null) {
342+
return;
328343
}
329344

330345
if(!methods.contains(methodName) && !methodName.toLowerCase().contains("render")) {
@@ -337,7 +352,7 @@ private void visitMethodReference(@NotNull MethodReference methodReference) {
337352
}
338353

339354
if (parameters[0] instanceof StringLiteralExpression) {
340-
addStringLiteralScope(methodReference, (StringLiteralExpression) parameters[0]);
355+
addStringLiteralScope(methodReference, (StringLiteralExpression) parameters[0], consumer);
341356
} else if(parameters[0] instanceof TernaryExpression) {
342357
// render(true === true ? 'foo.twig.html' : 'foobar.twig.html')
343358
for (PhpPsiElement phpPsiElement : new PhpPsiElement[]{((TernaryExpression) parameters[0]).getTrueVariant(), ((TernaryExpression) parameters[0]).getFalseVariant()}) {
@@ -346,60 +361,60 @@ private void visitMethodReference(@NotNull MethodReference methodReference) {
346361
}
347362

348363
if (phpPsiElement instanceof StringLiteralExpression) {
349-
addStringLiteralScope(methodReference, (StringLiteralExpression) phpPsiElement);
364+
addStringLiteralScope(methodReference, (StringLiteralExpression) phpPsiElement, consumer);
350365
} else if(phpPsiElement instanceof PhpReference) {
351-
resolvePhpReference(methodReference, phpPsiElement);
366+
resolvePhpReference(methodReference, phpPsiElement, consumer);
352367
}
353368
}
354369
} else if(parameters[0] instanceof AssignmentExpression) {
355370
// $this->render($template = 'foo.html.twig')
356371
PhpPsiElement value = ((AssignmentExpression) parameters[0]).getValue();
357372
if(value instanceof StringLiteralExpression) {
358-
addStringLiteralScope(methodReference, (StringLiteralExpression) value);
373+
addStringLiteralScope(methodReference, (StringLiteralExpression) value, consumer);
359374
}
360375
} else if(parameters[0] instanceof PhpReference) {
361-
resolvePhpReference(methodReference, parameters[0]);
376+
resolvePhpReference(methodReference, parameters[0], consumer);
362377
} else if(parameters[0] instanceof BinaryExpression) {
363378
// render($foo ?? 'foo.twig.html')
364379
PsiElement phpPsiElement = ((BinaryExpression) parameters[0]).getRightOperand();
365380

366381
if (phpPsiElement instanceof StringLiteralExpression) {
367-
addStringLiteralScope(methodReference, (StringLiteralExpression) phpPsiElement);
382+
addStringLiteralScope(methodReference, (StringLiteralExpression) phpPsiElement, consumer);
368383
} else if(phpPsiElement instanceof PhpReference) {
369-
resolvePhpReference(methodReference, phpPsiElement);
384+
resolvePhpReference(methodReference, phpPsiElement, consumer);
370385
}
371386
}
372387
}
373388

374-
private void resolvePhpReference(@NotNull MethodReference methodReference, PsiElement parameter) {
389+
private static void resolvePhpReference(@NotNull MethodReference methodReference, PsiElement parameter, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
375390
for (PhpNamedElement phpNamedElement : ((PhpReference) parameter).resolveLocal()) {
376391
// foo(self::foo)
377392
// foo($this->foo)
378393
if (phpNamedElement instanceof Field) {
379394
PsiElement defaultValue = ((Field) phpNamedElement).getDefaultValue();
380395
if (defaultValue instanceof StringLiteralExpression) {
381-
addStringLiteralScope(methodReference, (StringLiteralExpression) defaultValue);
396+
addStringLiteralScope(methodReference, (StringLiteralExpression) defaultValue, consumer);
382397
}
383398
} else if (phpNamedElement instanceof Variable) {
384399
// foo($var) => $var = 'test.html.twig'
385400
PsiElement assignmentExpression = phpNamedElement.getParent();
386401
if (assignmentExpression instanceof AssignmentExpression) {
387402
PhpPsiElement value = ((AssignmentExpression) assignmentExpression).getValue();
388403
if (value instanceof StringLiteralExpression) {
389-
addStringLiteralScope(methodReference, (StringLiteralExpression) value);
404+
addStringLiteralScope(methodReference, (StringLiteralExpression) value, consumer);
390405
}
391406
}
392407
} else if (phpNamedElement instanceof Parameter) {
393408
// function foobar($defaultParameter = 'default-function-parameter.html.twig')
394409
PsiElement value = ((Parameter) phpNamedElement).getDefaultValue();
395410
if (value instanceof StringLiteralExpression) {
396-
addStringLiteralScope(methodReference, (StringLiteralExpression) value);
411+
addStringLiteralScope(methodReference, (StringLiteralExpression) value, consumer);
397412
}
398413
}
399414
}
400415
}
401416

402-
private void addStringLiteralScope(@NotNull MethodReference methodReference, @NotNull StringLiteralExpression defaultValue) {
417+
private static void addStringLiteralScope(@NotNull MethodReference methodReference, @NotNull StringLiteralExpression defaultValue, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
403418
String contents = defaultValue.getContents();
404419
if (StringUtils.isBlank(contents) || !contents.endsWith(".twig")) {
405420
return;
@@ -410,14 +425,18 @@ private void addStringLiteralScope(@NotNull MethodReference methodReference, @No
410425
return;
411426
}
412427

413-
addTemplateWithScope(contents, parentOfType, methodReference);
428+
addTemplateWithScope(contents, parentOfType, methodReference, consumer);
414429
}
415430

416431
/**
417432
* "@Template("foobar.html.twig")"
418433
* "@Template(template="foobar.html.twig")"
419434
*/
420435
private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) {
436+
processDocTag(phpDocTag, consumer);
437+
}
438+
439+
public static void processDocTag(@NotNull PhpDocTag phpDocTag, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
421440
// "@var" and user non related tags dont need an action
422441
if(AnnotationBackportUtil.NON_ANNOTATION_TAGS.contains(phpDocTag.getName())) {
423442
return;
@@ -440,17 +459,17 @@ private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) {
440459
// App\Controller\MyNiceController::myAction => my_nice/my.html.twig
441460
Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag);
442461
if(methodScope != null) {
443-
visitMethodForGuessing(methodScope);
462+
visitMethodForGuessing(methodScope, consumer);
444463
}
445464
} else if(template.endsWith(".twig")) {
446465
Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag);
447466
if(methodScope != null) {
448-
addTemplateWithScope(template, methodScope, null);
467+
addTemplateWithScope(template, methodScope, null, consumer);
449468
}
450469
}
451470
}
452471

453-
private void visitMethodForGuessing(@NotNull Method methodScope) {
472+
private static void visitMethodForGuessing(@NotNull Method methodScope, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
454473
PhpClass phpClass = methodScope.getContainingClass();
455474
if (phpClass != null) {
456475
// App\Controller\ "MyNice" Controller
@@ -461,16 +480,16 @@ private void visitMethodForGuessing(@NotNull Method methodScope) {
461480

462481
// __invoke is using controller as template name
463482
if (name.equals("__invoke")) {
464-
addTemplateWithScope(group + ".html.twig", methodScope, null);
483+
addTemplateWithScope(group + ".html.twig", methodScope, null, consumer);
465484
} else {
466485
String action = name.endsWith("Action") ? name.substring(0, name.length() - "Action".length()) : name;
467-
addTemplateWithScope(group + "/" + underscore(action) + ".html.twig", methodScope, null);
486+
addTemplateWithScope(group + "/" + underscore(action) + ".html.twig", methodScope, null, consumer);
468487
}
469488
}
470489
}
471490
}
472491

473-
private void addTemplateWithScope(@NotNull String contents, @NotNull PhpNamedElement scope, @Nullable FunctionReference functionReference) {
492+
private static void addTemplateWithScope(@NotNull String contents, @NotNull PhpNamedElement scope, @Nullable FunctionReference functionReference, Consumer<Triple<String, PhpNamedElement, FunctionReference>> consumer) {
474493
String s = TwigUtil.normalizeTemplateName(contents);
475494
consumer.accept(new Triple<>(s, scope, functionReference));
476495
}

0 commit comments

Comments
 (0)