From 26d408b416845aebf32cd95558a99f388809e505 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko Date: Wed, 4 Mar 2020 22:58:18 +0200 Subject: [PATCH 1/3] Line Markers for GraphQl navigation --- META-INF/plugin.xml | 4 + ...raphQlResolverClassLineMarkerProvider.java | 90 ++++++++++ ...raphQlResolverUsageLineMarkerProvider.java | 93 ++++++++++ .../indexes/graphql/GraphQlResolverIndex.java | 164 ++++++++++++++++++ .../util/magento/graphql/GraphQlUtil.java | 39 +++++ 5 files changed, 390 insertions(+) create mode 100644 src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java create mode 100644 src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java create mode 100644 src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java create mode 100644 src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index 8c70dea6f..64c6017c7 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -29,6 +29,7 @@ com.jetbrains.php JavaScript com.intellij.modules.platform + com.intellij.lang.jsgraphql + + + diff --git a/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java b/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java new file mode 100644 index 000000000..700fcd4e1 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java @@ -0,0 +1,90 @@ +package com.magento.idea.magento2plugin.graphql.linemarker; + +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.daemon.LineMarkerProvider; +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; +import com.intellij.lang.jsgraphql.psi.GraphQLArgument; +import com.intellij.lang.jsgraphql.psi.GraphQLStringValue; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.PhpIcons; +import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.magento.idea.magento2plugin.project.Settings; +import com.magento.idea.magento2plugin.util.magento.graphql.GraphQlUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.*; + +public class GraphQlResolverClassLineMarkerProvider implements LineMarkerProvider { + @Nullable + @Override + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement psiElement) { + return null; + } + + @Override + public void collectSlowLineMarkers(@NotNull List psiElements, @NotNull Collection collection) { + if (psiElements.size() > 0) { + if (!Settings.isEnabled(psiElements.get(0).getProject())) { + return; + } + } + + for (PsiElement psiElement : psiElements) { + if (psiElement instanceof GraphQLArgument) { + Collection results; + + GraphQlResolverClassCollector collector = new GraphQlResolverClassCollector(); + results = collector.getGraphQLResolverClasses((GraphQLArgument) psiElement); + GraphQLStringValue argumentStringValue = GraphQlUtil.fetchResolverQuotedStringFromArgument(psiElement); + if (argumentStringValue == null) { + continue; + } + + if (results.size() > 0 ) { + collection.add(NavigationGutterIconBuilder + .create(PhpIcons.CLASS) + .setTargets(results) + .setTooltipText("Navigate to class") + .createLineMarkerInfo(PsiTreeUtil.getDeepestLast(argumentStringValue)) + ); + } + } + } + } + + private static class GraphQlResolverClassCollector { + + private HashMap> routesCache = new HashMap<>(); + + + Collection getGraphQLResolverClasses(@NotNull GraphQLArgument graphQLArgument) { + List graphQLResolverClasses = new ArrayList<>(); + GraphQLStringValue argumentStringValue = GraphQlUtil.fetchResolverQuotedStringFromArgument(graphQLArgument); + if (argumentStringValue == null) { + return graphQLResolverClasses; + } + + graphQLResolverClasses.addAll(getUsages(argumentStringValue)); + + return graphQLResolverClasses; + } + + Collection getUsages(@NotNull GraphQLStringValue graphQLStringValue) { + String phpClassFQN = GraphQlUtil.resolverStringToPhpFQN(graphQLStringValue.getText()); + if (!routesCache.containsKey(phpClassFQN)) { + + Collection phpClasses = extractClasses(phpClassFQN, graphQLStringValue.getProject()); + routesCache.put(phpClassFQN, phpClasses); + } + return routesCache.get(phpClassFQN); + } + + Collection extractClasses(@NotNull String phpClassFQN, Project project) { + PhpIndex phpIndex = PhpIndex.getInstance(project); + return phpIndex.getClassesByFQN(phpClassFQN); + } + } +} diff --git a/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java b/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java new file mode 100644 index 000000000..2bc4b9396 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java @@ -0,0 +1,93 @@ +package com.magento.idea.magento2plugin.php.linemarker; + +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.daemon.LineMarkerProvider; +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; +import com.intellij.lang.jsgraphql.GraphQLIcons; +import com.intellij.lang.jsgraphql.psi.GraphQLQuotedString; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.magento.idea.magento2plugin.project.Settings; +import com.magento.idea.magento2plugin.stubs.indexes.graphql.GraphQlResolverIndex; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +public class GraphQlResolverUsageLineMarkerProvider implements LineMarkerProvider { + @Nullable + @Override + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement psiElement) { + return null; + } + + @Override + public void collectSlowLineMarkers(@NotNull List psiElements, @NotNull Collection collection) { + if (psiElements.size() > 0) { + if (!Settings.isEnabled(psiElements.get(0).getProject())) { + return; + } + } + + for (PsiElement psiElement : psiElements) { + if (psiElement instanceof PhpClass) { + List results; + + if (!isResolver((PhpClass) psiElement)) { + return; + } + GraphQlUsagesCollector collector = new GraphQlUsagesCollector(); + results = collector.getGraphQLUsages((PhpClass) psiElement); + + if (results.size() > 0 ) { + collection.add(NavigationGutterIconBuilder + .create(GraphQLIcons.FILE) + .setTargets(results) + .setTooltipText("Navigate to schema") + .createLineMarkerInfo(PsiTreeUtil.getDeepestFirst(psiElement)) + ); + } + } + } + } + + private boolean isResolver(PhpClass psiElement) { + PhpClass[] implementedInterfaces = psiElement.getImplementedInterfaces(); + for (PhpClass implementedInterface: implementedInterfaces) { + if (!implementedInterface.getFQN().equals("\\Magento\\Framework\\GraphQl\\Query\\ResolverInterface")) { + continue; + } + return true; + } + return false; + } + + private static class GraphQlUsagesCollector { + + private HashMap> routesCache = new HashMap<>(); + + List getGraphQLUsages(@NotNull PhpClass phpClass) { + List graphQLQuotedStrings = new ArrayList<>(); + + graphQLQuotedStrings.addAll(getUsages(phpClass)); + + return graphQLQuotedStrings; + } + + List getUsages(@NotNull PhpClass phpClass) { + String phpClassFQN = phpClass.getFQN(); + if (!routesCache.containsKey(phpClassFQN)) { + List graphQLStringValues = extractGraphQLQuotesStringsForClass(phpClass); + routesCache.put(phpClassFQN, graphQLStringValues); + } + return routesCache.get(phpClassFQN); + } + + List extractGraphQLQuotesStringsForClass(@NotNull PhpClass phpClass) { + return GraphQlResolverIndex.getGraphQLUsages(phpClass); + } + } +} diff --git a/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java b/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java new file mode 100644 index 000000000..e946bd2ba --- /dev/null +++ b/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java @@ -0,0 +1,164 @@ +package com.magento.idea.magento2plugin.stubs.indexes.graphql; + +import com.intellij.lang.jsgraphql.GraphQLFileType; +import com.intellij.lang.jsgraphql.psi.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.indexing.*; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.KeyDescriptor; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.magento.idea.magento2plugin.util.magento.graphql.GraphQlUtil; +import org.jetbrains.annotations.NotNull; +import java.util.*; + +public class GraphQlResolverIndex extends ScalarIndexExtension { + public static final ID KEY + = ID.create("com.magento.idea.magento2plugin.stubs.indexes.resolver_usages"); + + @NotNull + @Override + public ID getName() { + return KEY; + } + + @NotNull + @Override + public DataIndexer getIndexer() { + return inputData -> { + Map map = new HashMap<>(); + + GraphQLFile qraphQLFile = (GraphQLFile) inputData.getPsiFile(); + PsiElement[] children = qraphQLFile.getChildren(); + for (PsiElement child : children) { + if (!(child instanceof GraphQLObjectTypeDefinition)) { + continue; + } + PsiElement[] objectChildren = child.getChildren(); + for (PsiElement objectChild : objectChildren) { + if (!(objectChild instanceof GraphQLFieldsDefinition)) { + continue; + } + PsiElement[] fieldsChildren = objectChild.getChildren(); + for (PsiElement fieldsChild : fieldsChildren) { + if (!(fieldsChild instanceof GraphQLFieldDefinition)) { + continue; + } + PsiElement[] fieldChildren = fieldsChild.getChildren(); + for (PsiElement fieldChild : fieldChildren) { + if (!(fieldChild instanceof GraphQLDirective)) { + continue; + } + if (!fieldChild.getText().startsWith("@resolver")) { + continue; + } + + PsiElement[] directiveChildren = fieldChild.getChildren(); + + for (PsiElement directiveChild : directiveChildren) { + if (!(directiveChild instanceof GraphQLArguments)) { + continue; + } + + PsiElement[] argumentsChildren = directiveChild.getChildren(); + for (PsiElement argumentsChild : argumentsChildren) { + if (!(argumentsChild instanceof GraphQLArgument)) { + continue; + } + + PsiElement argumentStringValue = GraphQlUtil.fetchResolverQuotedStringFromArgument(argumentsChild); + if (argumentStringValue == null) continue; + + String resolverFQN = argumentStringValue.getText(); + if (resolverFQN == null) { + return null; + } + + resolverFQN = GraphQlUtil.resolverStringToPhpFQN(resolverFQN); + + map.put(resolverFQN, null); + } + } + } + } + } + } + + return map; + }; + } + + @NotNull + @Override + public KeyDescriptor getKeyDescriptor() { + return new EnumeratorStringDescriptor(); + } + + @NotNull + @Override + public FileBasedIndex.InputFilter getInputFilter() { + return virtualFile -> (virtualFile.getFileType() == GraphQLFileType.INSTANCE + && virtualFile.getName().equals("schema.graphqls")); + } + + @Override + public boolean dependsOnFileContent() { + return true; + } + + @Override + public int getVersion() { + return 1; + } + + + public static List getGraphQLUsages(@NotNull PhpClass phpClass) { + List quotedStrings = new ArrayList<>(); + + String classFqn = phpClass.getFQN(); + Collection containingFiles = FileBasedIndex + .getInstance().getContainingFiles(KEY, classFqn, GlobalSearchScope.allScope(phpClass.getProject())); + + PsiManager psiManager = PsiManager.getInstance(phpClass.getProject()); + for (VirtualFile virtualFile : containingFiles) { + GraphQLFile file = (GraphQLFile) psiManager.findFile(virtualFile); + if (file == null) { + continue; + } + PsiElement[] children = file.getChildren(); + findMatchingQuotedString(children, classFqn, quotedStrings); + } + return quotedStrings; + } + + private static PsiElement findMatchingQuotedString(PsiElement[] psiElements, String classFqn, List quotedStrings) { + for (PsiElement element: psiElements) { + if (!(element instanceof GraphQLArgument) && element.getChildren().length == 0) { + continue; + } + if (!(element instanceof GraphQLArgument) && element.getChildren().length != 0) { + findMatchingQuotedString(element.getChildren(), classFqn, quotedStrings); + continue; + } + + PsiElement argumentStringValue = GraphQlUtil.fetchResolverQuotedStringFromArgument(element); + if (argumentStringValue == null) continue; + + String resolverFQN = argumentStringValue.getText(); + if (resolverFQN == null) { + return null; + } + + resolverFQN = GraphQlUtil.resolverStringToPhpFQN(resolverFQN); + + if (!resolverFQN.equals(classFqn)) { + continue; + } + + quotedStrings.add((GraphQLQuotedString) argumentStringValue.getFirstChild()); + } + return null; + } +} diff --git a/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java b/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java new file mode 100644 index 000000000..490ea8816 --- /dev/null +++ b/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java @@ -0,0 +1,39 @@ +package com.magento.idea.magento2plugin.util.magento.graphql; + +import com.intellij.lang.jsgraphql.psi.GraphQLStringValue; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GraphQlUtil { + + @NotNull + public static String resolverStringToPhpFQN(String resolverFQN) { + resolverFQN = resolverFQN.replace("\\\\", "\\").replace("\"",""); + if (!resolverFQN.startsWith("\\")) { + resolverFQN = "\\".concat(resolverFQN); + } + return resolverFQN; + } + + @Nullable + public static GraphQLStringValue fetchResolverQuotedStringFromArgument(PsiElement argument) { + PsiElement[] argumentChildren = argument.getChildren(); + + if (argumentChildren.length < 2) { + return null; + } + PsiElement argumentIdentifier = argumentChildren[0]; + if (!(argumentChildren[1] instanceof GraphQLStringValue)) { + return null; + } + + GraphQLStringValue argumentStringValue = (GraphQLStringValue) argumentChildren[1]; + + if (!argumentIdentifier.getText().equals("class")) { + return null; + } + + return argumentStringValue; + } +} From 60e9efecf495c7c55f5564c712562b282d6d2786 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko Date: Thu, 5 Mar 2020 22:52:09 +0200 Subject: [PATCH 2/3] Fixed manual reindex --- .../magento/idea/magento2plugin/indexes/IndexManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/magento/idea/magento2plugin/indexes/IndexManager.java b/src/com/magento/idea/magento2plugin/indexes/IndexManager.java index 5a6120d35..3d94b4ca4 100644 --- a/src/com/magento/idea/magento2plugin/indexes/IndexManager.java +++ b/src/com/magento/idea/magento2plugin/indexes/IndexManager.java @@ -3,6 +3,7 @@ import com.intellij.util.indexing.FileBasedIndexImpl; import com.intellij.util.indexing.ID; import com.magento.idea.magento2plugin.stubs.indexes.*; +import com.magento.idea.magento2plugin.stubs.indexes.graphql.GraphQlResolverIndex; import com.magento.idea.magento2plugin.stubs.indexes.xml.PhpClassNameIndex; /** @@ -25,7 +26,9 @@ public static void manualReindex() { // webapi WebApiTypeIndex.KEY, ModuleNameIndex.KEY, - PhpClassNameIndex.KEY + PhpClassNameIndex.KEY, + //graphql + GraphQlResolverIndex.KEY }; for (ID id: indexIds) { From 0ca7309573344cb31ae7fe8c952563f81fc59929 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko Date: Wed, 18 Mar 2020 22:09:19 +0200 Subject: [PATCH 3/3] Fixed copyrights graphQl --- .../linemarker/GraphQlResolverClassLineMarkerProvider.java | 4 ++++ .../linemarker/GraphQlResolverUsageLineMarkerProvider.java | 4 ++++ .../stubs/indexes/graphql/GraphQlResolverIndex.java | 4 ++++ .../idea/magento2plugin/util/magento/graphql/GraphQlUtil.java | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java b/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java index 700fcd4e1..104ab3e1f 100644 --- a/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java +++ b/src/com/magento/idea/magento2plugin/graphql/linemarker/GraphQlResolverClassLineMarkerProvider.java @@ -1,3 +1,7 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ package com.magento.idea.magento2plugin.graphql.linemarker; import com.intellij.codeInsight.daemon.LineMarkerInfo; diff --git a/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java b/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java index 2bc4b9396..0124cb465 100644 --- a/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java +++ b/src/com/magento/idea/magento2plugin/php/linemarker/GraphQlResolverUsageLineMarkerProvider.java @@ -1,3 +1,7 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ package com.magento.idea.magento2plugin.php.linemarker; import com.intellij.codeInsight.daemon.LineMarkerInfo; diff --git a/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java b/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java index e946bd2ba..160646457 100644 --- a/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java +++ b/src/com/magento/idea/magento2plugin/stubs/indexes/graphql/GraphQlResolverIndex.java @@ -1,3 +1,7 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ package com.magento.idea.magento2plugin.stubs.indexes.graphql; import com.intellij.lang.jsgraphql.GraphQLFileType; diff --git a/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java b/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java index 490ea8816..65918f0e0 100644 --- a/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java +++ b/src/com/magento/idea/magento2plugin/util/magento/graphql/GraphQlUtil.java @@ -1,3 +1,7 @@ +/* + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ package com.magento.idea.magento2plugin.util.magento.graphql; import com.intellij.lang.jsgraphql.psi.GraphQLStringValue;