Skip to content

Commit dd0f42b

Browse files
committed
add support for Twig extension registered via Attributes
1 parent c58958a commit dd0f42b

File tree

8 files changed

+231
-7
lines changed

8 files changed

+231
-7
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package fr.adrienbrault.idea.symfony2plugin.stubs.indexes;
2+
3+
import com.intellij.util.indexing.*;
4+
import com.intellij.util.io.DataExternalizer;
5+
import com.intellij.util.io.EnumeratorStringDescriptor;
6+
import com.intellij.util.io.KeyDescriptor;
7+
import com.jetbrains.php.lang.PhpFileType;
8+
import com.jetbrains.php.lang.psi.PhpFile;
9+
import com.jetbrains.php.lang.psi.PhpPsiUtil;
10+
import com.jetbrains.php.lang.psi.elements.*;
11+
import fr.adrienbrault.idea.symfony2plugin.util.PhpPsiAttributesUtil;
12+
import org.apache.commons.lang3.StringUtils;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
/**
19+
* @author Daniel Espendiller <daniel@espendiller.net>
20+
*/
21+
public class TwigAttributeIndex extends FileBasedIndexExtension<String, String> {
22+
public static final ID<String, String> KEY = ID.create("fr.adrienbrault.idea.symfony2plugin.twig_attribute_extension.index");
23+
24+
@Override
25+
public @NotNull ID<String, String> getName() {
26+
return KEY;
27+
}
28+
29+
@Override
30+
public @NotNull DataIndexer<String, String, FileContent> getIndexer() {
31+
return new TwigAttributeIndexer();
32+
}
33+
34+
@Override
35+
public @NotNull KeyDescriptor<String> getKeyDescriptor() {
36+
return EnumeratorStringDescriptor.INSTANCE;
37+
}
38+
39+
@Override
40+
public @NotNull DataExternalizer<String> getValueExternalizer() {
41+
return EnumeratorStringDescriptor.INSTANCE;
42+
}
43+
44+
@Override
45+
public @NotNull FileBasedIndex.InputFilter getInputFilter() {
46+
return virtualFile -> virtualFile.getFileType() == PhpFileType.INSTANCE;
47+
}
48+
49+
@Override
50+
public boolean dependsOnFileContent() {
51+
return true;
52+
}
53+
54+
@Override
55+
public int getVersion() {
56+
return 1;
57+
}
58+
59+
public static class TwigAttributeIndexer implements DataIndexer<String, String, FileContent> {
60+
private static final String[] SUPPORTED_ATTRIBUTES = {
61+
"\\Twig\\Attribute\\AsTwigFilter",
62+
"\\Twig\\Attribute\\AsTwigFunction",
63+
"\\Twig\\Attribute\\AsTwigTest",
64+
};
65+
66+
@Override
67+
public @NotNull Map<String, String> map(@NotNull FileContent inputData) {
68+
Map<String, String> result = new HashMap<>();
69+
if (!(inputData.getPsiFile() instanceof PhpFile phpFile)) {
70+
return result;
71+
}
72+
73+
for (PhpClass phpClass : PhpPsiUtil.findAllClasses(phpFile)) {
74+
for (Method method : phpClass.getMethods()) {
75+
processMethodAttributes(phpClass, method, result);
76+
}
77+
}
78+
79+
return result;
80+
}
81+
82+
private void processMethodAttributes(@NotNull PhpClass phpClass, @NotNull Method method, @NotNull Map<String, String> result) {
83+
for (PhpAttribute attribute : method.getAttributes()) {
84+
String attributeFqn = getAttributeFqn(attribute);
85+
if (attributeFqn == null) {
86+
continue;
87+
}
88+
89+
for (String supportedAttr : SUPPORTED_ATTRIBUTES) {
90+
if (supportedAttr.equals(attributeFqn)) {
91+
String nameAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsString(attribute, 0, "name");
92+
if (nameAttribute != null) {
93+
result.put(nameAttribute, String.format("#M#C\\%s.%s", StringUtils.stripStart(phpClass.getFQN(), "\\"), method.getName()));
94+
}
95+
96+
break;
97+
}
98+
}
99+
}
100+
}
101+
102+
private String getAttributeFqn(@NotNull PhpAttribute attribute) {
103+
ClassReference reference = attribute.getClassReference();
104+
return reference != null ? reference.getFQN() : null;
105+
}
106+
}
107+
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/util/IndexUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public static void forceReindex() {
3030
TwigIncludeStubIndex.KEY,
3131
TwigMacroFunctionStubIndex.KEY,
3232
TranslationStubIndex.KEY,
33-
TwigBlockIndexExtension.KEY
33+
TwigBlockIndexExtension.KEY,
34+
TwigAttributeIndex.KEY
3435
};
3536

3637
for(ID<?,?> id: indexIds) {

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
import com.intellij.openapi.util.Key;
55
import com.intellij.patterns.PlatformPatterns;
66
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.search.GlobalSearchScope;
78
import com.intellij.psi.util.*;
89
import com.jetbrains.php.PhpIcons;
910
import com.jetbrains.php.PhpIndex;
11+
import com.jetbrains.php.lang.PhpFileType;
1012
import com.jetbrains.php.lang.parser.PhpElementTypes;
1113
import com.jetbrains.php.lang.psi.PhpPsiUtil;
1214
import com.jetbrains.php.lang.psi.elements.*;
15+
import fr.adrienbrault.idea.symfony2plugin.stubs.cache.FileIndexCaches;
16+
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex;
1317
import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtension;
1418
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1519
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
@@ -30,6 +34,15 @@ public class TwigExtensionParser {
3034
private static final Key<CachedValue<Map<String, TwigExtension>>> FILTERS_CACHE = new Key<>("TWIG_EXTENSIONS_FILTERS");
3135
private static final Key<CachedValue<Map<String, TwigExtension>>> OPERATORS_CACHE = new Key<>("TWIG_EXTENSIONS_OPERATORS");
3236

37+
private static final Key<CachedValue<Map<String, List<String>>>> TWIG_ATTRIBUTE_FUNCTION_INDEX = new Key<>("TWIG_ATTRIBUTE_FUNCTION_INDEX");
38+
private static final Key<CachedValue<Set<String>>> TWIG_ATTRIBUTE_FUNCTION_INDEX_NAMES = new Key<>("TWIG_ATTRIBUTE_FUNCTION_INDEX_NAMES");
39+
40+
private static final Key<CachedValue<Map<String, List<String>>>> TWIG_ATTRIBUTE_FILTER_INDEX = new Key<>("TWIG_ATTRIBUTE_FILTER_INDEX");
41+
private static final Key<CachedValue<Set<String>>> TWIG_ATTRIBUTE_FILTER_NAMES = new Key<>("TWIG_ATTRIBUTE_FILTER_NAMES");
42+
43+
private static final Key<CachedValue<Map<String, List<String>>>> TWIG_ATTRIBUTE_TEST_INDEX = new Key<>("TWIG_ATTRIBUTE_TEST_INDEX");
44+
private static final Key<CachedValue<Set<String>>> TWIG_ATTRIBUTE_TEST_NAMES = new Key<>("TWIG_ATTRIBUTE_FUNCTION_FILTER_NAMES");
45+
3346
public enum TwigExtensionType {
3447
FUNCTION_METHOD, FUNCTION_NODE, SIMPLE_FUNCTION, FILTER, SIMPLE_TEST, OPERATOR
3548
}
@@ -39,7 +52,7 @@ public static Map<String, TwigExtension> getFunctions(@NotNull Project project)
3952
return CachedValuesManager.getManager(project).getCachedValue(
4053
project,
4154
FUNCTION_CACHE,
42-
() -> CachedValueProvider.Result.create(parseFunctions(TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT),
55+
() -> CachedValueProvider.Result.create(parseFunctions(project, TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT),
4356
false
4457
);
4558
}
@@ -49,7 +62,7 @@ public static Map<String, TwigExtension> getFilters(@NotNull Project project) {
4962
return CachedValuesManager.getManager(project).getCachedValue(
5063
project,
5164
FILTERS_CACHE,
52-
() -> CachedValueProvider.Result.create(parseFilters(TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT),
65+
() -> CachedValueProvider.Result.create(parseFilters(project, TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT),
5366
false
5467
);
5568
}
@@ -59,7 +72,7 @@ public static Map<String, TwigExtension> getSimpleTest(@NotNull Project project)
5972
return CachedValuesManager.getManager(project).getCachedValue(
6073
project,
6174
TEST_CACHE,
62-
() -> CachedValueProvider.Result.create(parseTests(TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT),
75+
() -> CachedValueProvider.Result.create(parseTests(project, TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT),
6376
false
6477
);
6578
}
@@ -75,7 +88,7 @@ public static Map<String, TwigExtension> getOperators(@NotNull Project project)
7588
}
7689

7790
@NotNull
78-
private static Map<String, TwigExtension> parseFilters(@NotNull Collection<PhpClass> phpClasses) {
91+
private static Map<String, TwigExtension> parseFilters(@NotNull Project project, @NotNull Collection<PhpClass> phpClasses) {
7992
Map<String, TwigExtension> extensions = new HashMap<>();
8093

8194
for (PhpClass phpClass : phpClasses) {
@@ -92,11 +105,15 @@ private static Map<String, TwigExtension> parseFilters(@NotNull Collection<PhpCl
92105
}
93106
}
94107

108+
for (Map.Entry<String, List<String>> entry : FileIndexCaches.getStringDataCache(project, TWIG_ATTRIBUTE_FILTER_INDEX, TWIG_ATTRIBUTE_FILTER_NAMES, TwigAttributeIndex.KEY, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE)).entrySet()) {
109+
extensions.put(entry.getKey(), new TwigExtension(TwigExtensionType.FILTER, entry.getValue().getFirst()));
110+
}
111+
95112
return Collections.unmodifiableMap(extensions);
96113
}
97114

98115
@NotNull
99-
private static Map<String, TwigExtension> parseFunctions(@NotNull Collection<PhpClass> phpClasses) {
116+
private static Map<String, TwigExtension> parseFunctions(@NotNull Project project, @NotNull Collection<PhpClass> phpClasses) {
100117
Map<String, TwigExtension> extensions = new HashMap<>();
101118

102119
for (PhpClass phpClass : phpClasses) {
@@ -113,11 +130,15 @@ private static Map<String, TwigExtension> parseFunctions(@NotNull Collection<Php
113130
}
114131
}
115132

133+
for (Map.Entry<String, List<String>> entry : FileIndexCaches.getStringDataCache(project, TWIG_ATTRIBUTE_FUNCTION_INDEX, TWIG_ATTRIBUTE_FUNCTION_INDEX_NAMES, TwigAttributeIndex.KEY, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE)).entrySet()) {
134+
extensions.put(entry.getKey(), new TwigExtension(TwigExtensionType.FUNCTION_METHOD, entry.getValue().getFirst()));
135+
}
136+
116137
return Collections.unmodifiableMap(extensions);
117138
}
118139

119140
@NotNull
120-
private static Map<String, TwigExtension> parseTests(@NotNull Collection<PhpClass> phpClasses) {
141+
private static Map<String, TwigExtension> parseTests(@NotNull Project project, @NotNull Collection<PhpClass> phpClasses) {
121142
Map<String, TwigExtension> extensions = new HashMap<>();
122143

123144
for (PhpClass phpClass : phpClasses) {
@@ -129,6 +150,10 @@ private static Map<String, TwigExtension> parseTests(@NotNull Collection<PhpClas
129150
}
130151
}
131152

153+
for (Map.Entry<String, List<String>> entry : FileIndexCaches.getStringDataCache(project, TWIG_ATTRIBUTE_TEST_INDEX, TWIG_ATTRIBUTE_TEST_NAMES, TwigAttributeIndex.KEY, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE)).entrySet()) {
154+
extensions.put(entry.getKey(), new TwigExtension(TwigExtensionType.SIMPLE_TEST, entry.getValue().getFirst()));
155+
}
156+
132157
return Collections.unmodifiableMap(extensions);
133158
}
134159

src/main/resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@
270270
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.UxTemplateStubIndex"/>
271271
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigBlockEmbedIndex"/>
272272
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.ConfigStubIndex"/>
273+
<fileBasedIndex implementation="fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex"/>
273274

274275
<codeInsight.lineMarkerProvider language="PHP" implementationClass="fr.adrienbrault.idea.symfony2plugin.config.ServiceLineMarkerProvider"/>
275276
<codeInsight.lineMarkerProvider language="PHP" implementationClass="fr.adrienbrault.idea.symfony2plugin.dic.ControllerMethodLineMarkerProvider"/>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package fr.adrienbrault.idea.symfony2plugin.tests.stubs.indexes;
2+
3+
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex;
4+
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
5+
6+
/**
7+
* @author Daniel Espendiller <daniel@espendiller.net>
8+
* @see fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex
9+
*/
10+
public class TwigAttributeIndexTest extends SymfonyLightCodeInsightFixtureTestCase {
11+
public void setUp() throws Exception {
12+
super.setUp();
13+
14+
myFixture.copyFileToProject("TwigAttributeIndex.php");
15+
}
16+
17+
public void testThatValuesAreInIndex() {
18+
assertIndexContainsKeyWithValue(TwigAttributeIndex.KEY, "product_number_filter", "#M#C\\App\\Twig\\AppExtension.formatProductNumberFilter");
19+
assertIndexContainsKeyWithValue(TwigAttributeIndex.KEY, "product_number_function", "#M#C\\App\\Twig\\AppExtension.formatProductNumberFunction");
20+
assertIndexContainsKeyWithValue(TwigAttributeIndex.KEY, "product_number_test", "#M#C\\App\\Twig\\AppExtension.formatProductNumberTest");
21+
}
22+
23+
public String getTestDataPath() {
24+
return "src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures";
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace App\Twig;
4+
5+
use Twig\Attribute\AsTwigFilter;
6+
use Twig\Attribute\AsTwigFunction;
7+
use Twig\Attribute\AsTwigTest;
8+
9+
class AppExtension
10+
{
11+
#[AsTwigFilter('product_number_filter')]
12+
public function formatProductNumberFilter(string $number): string
13+
{
14+
}
15+
16+
#[AsTwigFunction('product_number_function')]
17+
public function formatProductNumberFunction(string $number): string
18+
{
19+
}
20+
21+
#[AsTwigTest('product_number_test')]
22+
public function formatProductNumberTest(string $number): string
23+
{
24+
}
25+
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/TwigExtensionParserTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ public void testExtensionAreCollected() {
7676
"#Fmax",
7777
TwigExtensionParser.getFunctions(getProject()).get("conditional_return").getSignature()
7878
);
79+
80+
assertEquals(
81+
"#M#C\\App\\Twig\\AppExtension.formatProductNumberFilter",
82+
TwigExtensionParser.getFilters(getProject()).get("product_number_filter").getSignature()
83+
);
84+
85+
assertEquals(
86+
"#M#C\\App\\Twig\\AppExtension.formatProductNumberFunction",
87+
TwigExtensionParser.getFunctions(getProject()).get("product_number_function").getSignature()
88+
);
89+
90+
assertEquals(
91+
"#M#C\\App\\Twig\\AppExtension.formatProductNumberTest",
92+
TwigExtensionParser.getSimpleTest(getProject()).get("product_number_test").getSignature()
93+
);
7994
}
8095

8196
public void testExtensionAreCollectedForDeprecated() {

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/templating/util/fixtures/twig_extensions.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,28 @@ public function foobar()
147147
namespace Twig\Extension {
148148
interface ExtensionInterface {}
149149
class AbstractExtension implements ExtensionInterface {}
150+
}
151+
152+
namespace App\Twig {
153+
use Twig\Attribute\AsTwigFilter;
154+
use Twig\Attribute\AsTwigFunction;
155+
use Twig\Attribute\AsTwigTest;
156+
157+
class AppExtension
158+
{
159+
#[AsTwigFilter('product_number_filter')]
160+
public function formatProductNumberFilter(string $number): string
161+
{
162+
}
163+
164+
#[AsTwigFunction('product_number_function')]
165+
public function formatProductNumberFunction(string $number): string
166+
{
167+
}
168+
169+
#[AsTwigTest('product_number_test')]
170+
public function formatProductNumberTest(string $number): string
171+
{
172+
}
173+
}
150174
}

0 commit comments

Comments
 (0)