diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigAttributeIndex.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigAttributeIndex.java new file mode 100644 index 000000000..d8d5c2492 --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/TwigAttributeIndex.java @@ -0,0 +1,107 @@ +package fr.adrienbrault.idea.symfony2plugin.stubs.indexes; + +import com.intellij.util.indexing.*; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.KeyDescriptor; +import com.jetbrains.php.lang.PhpFileType; +import com.jetbrains.php.lang.psi.PhpFile; +import com.jetbrains.php.lang.psi.PhpPsiUtil; +import com.jetbrains.php.lang.psi.elements.*; +import fr.adrienbrault.idea.symfony2plugin.util.PhpPsiAttributesUtil; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Daniel Espendiller + */ +public class TwigAttributeIndex extends FileBasedIndexExtension { + public static final ID KEY = ID.create("fr.adrienbrault.idea.symfony2plugin.twig_attribute_extension.index"); + + @Override + public @NotNull ID getName() { + return KEY; + } + + @Override + public @NotNull DataIndexer getIndexer() { + return new TwigAttributeIndexer(); + } + + @Override + public @NotNull KeyDescriptor getKeyDescriptor() { + return EnumeratorStringDescriptor.INSTANCE; + } + + @Override + public @NotNull DataExternalizer getValueExternalizer() { + return EnumeratorStringDescriptor.INSTANCE; + } + + @Override + public @NotNull FileBasedIndex.InputFilter getInputFilter() { + return virtualFile -> virtualFile.getFileType() == PhpFileType.INSTANCE; + } + + @Override + public boolean dependsOnFileContent() { + return true; + } + + @Override + public int getVersion() { + return 1; + } + + public static class TwigAttributeIndexer implements DataIndexer { + private static final String[] SUPPORTED_ATTRIBUTES = { + "\\Twig\\Attribute\\AsTwigFilter", + "\\Twig\\Attribute\\AsTwigFunction", + "\\Twig\\Attribute\\AsTwigTest", + }; + + @Override + public @NotNull Map map(@NotNull FileContent inputData) { + Map result = new HashMap<>(); + if (!(inputData.getPsiFile() instanceof PhpFile phpFile)) { + return result; + } + + for (PhpClass phpClass : PhpPsiUtil.findAllClasses(phpFile)) { + for (Method method : phpClass.getMethods()) { + processMethodAttributes(phpClass, method, result); + } + } + + return result; + } + + private void processMethodAttributes(@NotNull PhpClass phpClass, @NotNull Method method, @NotNull Map result) { + for (PhpAttribute attribute : method.getAttributes()) { + String attributeFqn = getAttributeFqn(attribute); + if (attributeFqn == null) { + continue; + } + + for (String supportedAttr : SUPPORTED_ATTRIBUTES) { + if (supportedAttr.equals(attributeFqn)) { + String nameAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsString(attribute, 0, "name"); + if (nameAttribute != null) { + result.put(nameAttribute, String.format("#M#C\\%s.%s", StringUtils.stripStart(phpClass.getFQN(), "\\"), method.getName())); + } + + break; + } + } + } + } + + private String getAttributeFqn(@NotNull PhpAttribute attribute) { + ClassReference reference = attribute.getClassReference(); + return reference != null ? reference.getFQN() : null; + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/util/IndexUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/util/IndexUtil.java index e1af5bedf..dbaf8e727 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/util/IndexUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/util/IndexUtil.java @@ -30,7 +30,8 @@ public static void forceReindex() { TwigIncludeStubIndex.KEY, TwigMacroFunctionStubIndex.KEY, TranslationStubIndex.KEY, - TwigBlockIndexExtension.KEY + TwigBlockIndexExtension.KEY, + TwigAttributeIndex.KEY }; for(ID id: indexIds) { diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigExtensionParser.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigExtensionParser.java index ada0ec5f2..cc9281e20 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigExtensionParser.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigExtensionParser.java @@ -4,12 +4,16 @@ import com.intellij.openapi.util.Key; import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.PsiElement; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.*; import com.jetbrains.php.PhpIcons; import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.PhpFileType; import com.jetbrains.php.lang.parser.PhpElementTypes; import com.jetbrains.php.lang.psi.PhpPsiUtil; import com.jetbrains.php.lang.psi.elements.*; +import fr.adrienbrault.idea.symfony2plugin.stubs.cache.FileIndexCaches; +import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex; import fr.adrienbrault.idea.symfony2plugin.templating.dict.TwigExtension; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; @@ -30,6 +34,15 @@ public class TwigExtensionParser { private static final Key>> FILTERS_CACHE = new Key<>("TWIG_EXTENSIONS_FILTERS"); private static final Key>> OPERATORS_CACHE = new Key<>("TWIG_EXTENSIONS_OPERATORS"); + private static final Key>>> TWIG_ATTRIBUTE_FUNCTION_INDEX = new Key<>("TWIG_ATTRIBUTE_FUNCTION_INDEX"); + private static final Key>> TWIG_ATTRIBUTE_FUNCTION_INDEX_NAMES = new Key<>("TWIG_ATTRIBUTE_FUNCTION_INDEX_NAMES"); + + private static final Key>>> TWIG_ATTRIBUTE_FILTER_INDEX = new Key<>("TWIG_ATTRIBUTE_FILTER_INDEX"); + private static final Key>> TWIG_ATTRIBUTE_FILTER_NAMES = new Key<>("TWIG_ATTRIBUTE_FILTER_NAMES"); + + private static final Key>>> TWIG_ATTRIBUTE_TEST_INDEX = new Key<>("TWIG_ATTRIBUTE_TEST_INDEX"); + private static final Key>> TWIG_ATTRIBUTE_TEST_NAMES = new Key<>("TWIG_ATTRIBUTE_FUNCTION_FILTER_NAMES"); + public enum TwigExtensionType { FUNCTION_METHOD, FUNCTION_NODE, SIMPLE_FUNCTION, FILTER, SIMPLE_TEST, OPERATOR } @@ -39,7 +52,7 @@ public static Map getFunctions(@NotNull Project project) return CachedValuesManager.getManager(project).getCachedValue( project, FUNCTION_CACHE, - () -> CachedValueProvider.Result.create(parseFunctions(TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT), + () -> CachedValueProvider.Result.create(parseFunctions(project, TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT), false ); } @@ -49,7 +62,7 @@ public static Map getFilters(@NotNull Project project) { return CachedValuesManager.getManager(project).getCachedValue( project, FILTERS_CACHE, - () -> CachedValueProvider.Result.create(parseFilters(TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT), + () -> CachedValueProvider.Result.create(parseFilters(project, TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT), false ); } @@ -59,7 +72,7 @@ public static Map getSimpleTest(@NotNull Project project) return CachedValuesManager.getManager(project).getCachedValue( project, TEST_CACHE, - () -> CachedValueProvider.Result.create(parseTests(TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT), + () -> CachedValueProvider.Result.create(parseTests(project, TwigUtil.getTwigExtensionClasses(project)), PsiModificationTracker.MODIFICATION_COUNT), false ); } @@ -75,7 +88,7 @@ public static Map getOperators(@NotNull Project project) } @NotNull - private static Map parseFilters(@NotNull Collection phpClasses) { + private static Map parseFilters(@NotNull Project project, @NotNull Collection phpClasses) { Map extensions = new HashMap<>(); for (PhpClass phpClass : phpClasses) { @@ -92,11 +105,15 @@ private static Map parseFilters(@NotNull Collection> entry : FileIndexCaches.getStringDataCache(project, TWIG_ATTRIBUTE_FILTER_INDEX, TWIG_ATTRIBUTE_FILTER_NAMES, TwigAttributeIndex.KEY, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE)).entrySet()) { + extensions.put(entry.getKey(), new TwigExtension(TwigExtensionType.FILTER, entry.getValue().getFirst())); + } + return Collections.unmodifiableMap(extensions); } @NotNull - private static Map parseFunctions(@NotNull Collection phpClasses) { + private static Map parseFunctions(@NotNull Project project, @NotNull Collection phpClasses) { Map extensions = new HashMap<>(); for (PhpClass phpClass : phpClasses) { @@ -113,11 +130,15 @@ private static Map parseFunctions(@NotNull Collection> entry : FileIndexCaches.getStringDataCache(project, TWIG_ATTRIBUTE_FUNCTION_INDEX, TWIG_ATTRIBUTE_FUNCTION_INDEX_NAMES, TwigAttributeIndex.KEY, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE)).entrySet()) { + extensions.put(entry.getKey(), new TwigExtension(TwigExtensionType.FUNCTION_METHOD, entry.getValue().getFirst())); + } + return Collections.unmodifiableMap(extensions); } @NotNull - private static Map parseTests(@NotNull Collection phpClasses) { + private static Map parseTests(@NotNull Project project, @NotNull Collection phpClasses) { Map extensions = new HashMap<>(); for (PhpClass phpClass : phpClasses) { @@ -129,6 +150,10 @@ private static Map parseTests(@NotNull Collection> entry : FileIndexCaches.getStringDataCache(project, TWIG_ATTRIBUTE_TEST_INDEX, TWIG_ATTRIBUTE_TEST_NAMES, TwigAttributeIndex.KEY, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE)).entrySet()) { + extensions.put(entry.getKey(), new TwigExtension(TwigExtensionType.SIMPLE_TEST, entry.getValue().getFirst())); + } + return Collections.unmodifiableMap(extensions); } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 9f35223f0..8484fd6ce 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -270,6 +270,7 @@ + diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/TwigAttributeIndexTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/TwigAttributeIndexTest.java new file mode 100644 index 000000000..80f65d256 --- /dev/null +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/TwigAttributeIndexTest.java @@ -0,0 +1,26 @@ +package fr.adrienbrault.idea.symfony2plugin.tests.stubs.indexes; + +import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex; +import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase; + +/** + * @author Daniel Espendiller + * @see fr.adrienbrault.idea.symfony2plugin.stubs.indexes.TwigAttributeIndex + */ +public class TwigAttributeIndexTest extends SymfonyLightCodeInsightFixtureTestCase { + public void setUp() throws Exception { + super.setUp(); + + myFixture.copyFileToProject("TwigAttributeIndex.php"); + } + + public void testThatValuesAreInIndex() { + assertIndexContainsKeyWithValue(TwigAttributeIndex.KEY, "product_number_filter", "#M#C\\App\\Twig\\AppExtension.formatProductNumberFilter"); + assertIndexContainsKeyWithValue(TwigAttributeIndex.KEY, "product_number_function", "#M#C\\App\\Twig\\AppExtension.formatProductNumberFunction"); + assertIndexContainsKeyWithValue(TwigAttributeIndex.KEY, "product_number_test", "#M#C\\App\\Twig\\AppExtension.formatProductNumberTest"); + } + + public String getTestDataPath() { + return "src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures"; + } +} diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/TwigAttributeIndex.php b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/TwigAttributeIndex.php new file mode 100644 index 000000000..05c4d9cbe --- /dev/null +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/TwigAttributeIndex.php @@ -0,0 +1,25 @@ +