From 8e9ccf6091f6173759783ddb7adac27059e24bbc Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Sun, 17 Mar 2024 14:11:00 +0100 Subject: [PATCH] [devkit] Implement Go to implementation for @Remote methods and find usages in tests for production methods GitOrigin-RevId: a3b28d0f93b8aa807f8b2b3f459ab56d69e49203 --- .../resources/intellij.devkit.core.xml | 11 +- .../driver/RemoteImplementationSearchers.kt | 100 +++++++++++++++ .../driver/RemoteMethodReferenceSearcher.kt | 116 ++++++++++++++++++ .../src/driver/RemoteUseScopeEnlarger.kt | 35 ++++++ 4 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 plugins/devkit/devkit-core/src/driver/RemoteImplementationSearchers.kt create mode 100644 plugins/devkit/devkit-core/src/driver/RemoteMethodReferenceSearcher.kt create mode 100644 plugins/devkit/devkit-core/src/driver/RemoteUseScopeEnlarger.kt diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index 7655b9f45035..e418a67410f2 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -60,9 +60,9 @@ - + + rootTagName="product-modules"/> @@ -86,6 +86,13 @@ + + + + + + + { + override fun execute(queryParameters: OverridingMethodsSearch.SearchParameters, consumer: Processor): Boolean { + return ReadAction.compute { + if (queryParameters.method.hasModifier(JvmModifier.ABSTRACT)) { + if (isRemoteInterface(queryParameters.method.containingClass)) { + val remoteMethods = findRemoteMethods(queryParameters.method) + for (m in remoteMethods) { + if (!consumer.process(m)) { + return@compute false + } + } + } + } + + return@compute true + } + } +} + +internal class RemoteMethodAllImplementationSearcher : QueryExecutor, AllOverridingMethodsSearch.SearchParameters> { + override fun execute(queryParameters: AllOverridingMethodsSearch.SearchParameters, consumer: Processor>): Boolean { + return ReadAction.compute { + if (isRemoteInterface(queryParameters.psiClass)) { + for (interfaceMethod in queryParameters.psiClass.allMethods) { + val remoteMethods = findRemoteMethods(interfaceMethod) + for (m in remoteMethods) { + if (!consumer.process(Couple(interfaceMethod, m))) { + return@compute false + } + } + } + } + + return@compute true + } + } +} + +internal fun findRemoteMethods(psiMethod: PsiMethod): Collection { + if (!psiMethod.isValid) return emptyList() + + val uClass = psiMethod.toUElementOfType()?.getContainingUClass() ?: return emptyList() + val remoteClass = getTargetRemoteClass(psiMethod.project, uClass) ?: return emptyList() + + return remoteClass.allMethods + .filter { it.name == psiMethod.name } + .filterNot { + it.hasModifier(JvmModifier.PRIVATE) + || it.hasModifier(JvmModifier.PROTECTED) + || it.hasModifier(JvmModifier.PACKAGE_LOCAL) + } + .filter { it.parameters.size == psiMethod.parameters.size } +} + +internal class RemoteInterfaceDirectImplementationSearcher : QueryExecutor { + override fun execute(queryParameters: DirectClassInheritorsSearch.SearchParameters, consumer: Processor): Boolean { + return processRemoteTargetClass(queryParameters.classToProcess, consumer) + } +} + +internal class RemoteInterfaceImplementationSearcher : QueryExecutor { + override fun execute(queryParameters: ClassInheritorsSearch.SearchParameters, consumer: Processor): Boolean { + return processRemoteTargetClass(queryParameters.classToProcess, consumer) + } +} + +private fun processRemoteTargetClass(classToProcess: PsiClass, + consumer: Processor): Boolean { + return ReadAction.compute { + if (isRemoteInterface(classToProcess)) { + val uClass = classToProcess.toUElementOfType() ?: return@compute true + val targetClass = getTargetRemoteClass(classToProcess.project, uClass) + if (targetClass != null) { + if (!consumer.process(targetClass)) { + return@compute false + } + } + } + return@compute true + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/driver/RemoteMethodReferenceSearcher.kt b/plugins/devkit/devkit-core/src/driver/RemoteMethodReferenceSearcher.kt new file mode 100644 index 000000000000..fa9ff92c778c --- /dev/null +++ b/plugins/devkit/devkit-core/src/driver/RemoteMethodReferenceSearcher.kt @@ -0,0 +1,116 @@ +package org.jetbrains.idea.devkit.driver + +import com.intellij.codeInsight.AnnotationUtil +import com.intellij.lang.jvm.JvmModifier +import com.intellij.openapi.application.QueryExecutorBase +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.FileIndexFacade +import com.intellij.openapi.roots.TestSourcesFilter +import com.intellij.openapi.util.TextRange +import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope.EMPTY_SCOPE +import com.intellij.psi.search.GlobalSearchScope.allScope +import com.intellij.psi.search.RequestResultProcessor +import com.intellij.psi.search.SearchScope +import com.intellij.psi.search.UsageSearchContext +import com.intellij.psi.search.searches.MethodReferencesSearch +import com.intellij.psi.util.InheritanceUtil.isInheritorOrSelf +import com.intellij.util.Processor +import org.jetbrains.uast.* + +internal class RemoteMethodReferenceSearcher : + QueryExecutorBase() { + override fun processQuery( + queryParameters: MethodReferencesSearch.SearchParameters, + consumer: Processor + ) { + val targetMethod = queryParameters.method + val methodName = ReadAction.compute { targetMethod.name } + + queryParameters.optimizer.searchWord( + methodName, + ReadAction.compute { + if (targetMethod.hasModifier(JvmModifier.PRIVATE) + || targetMethod.hasModifier(JvmModifier.PACKAGE_LOCAL) + || targetMethod.hasModifier(JvmModifier.PROTECTED) + ) { + return@compute EMPTY_SCOPE + } + + // do not search for self + val searchableClass = targetMethod.containingClass ?: return@compute EMPTY_SCOPE + if (isRemoteInterface(searchableClass)) return@compute EMPTY_SCOPE + + val project = targetMethod.project + val file = searchableClass.containingFile?.virtualFile ?: return@compute EMPTY_SCOPE + if (TestSourcesFilter.isTestSources(file, project) + || FileIndexFacade.getInstance(project).isInLibrary(file) + ) { + return@compute EMPTY_SCOPE + } + + val remoteClass = JavaPsiFacade.getInstance(project) + .findClass(REMOTE_ANNOTATION_FQN, allScope(project)) + ?: return@compute EMPTY_SCOPE + + // we don't care about resolve scope of the method itself + remoteClass.useScope.intersectWith(queryParameters.scopeDeterminedByUser) + }, + UsageSearchContext.IN_CODE, + true, + targetMethod, + object : RequestResultProcessor() { + override fun processTextOccurrence( + element: PsiElement, + offsetInElement: Int, + consumer: Processor + ): Boolean { + val method = element.toUElement(UMethod::class.java) ?: return true + + val uClass = method.getContainingUClass() ?: return true + val psiMethodFound = method.javaPsi + + if (psiMethodFound.parameters.size != targetMethod.parameters.size) { + // parameter count mismatch + return true + } + + val psiClass = psiMethodFound.containingClass ?: return true + if (!isRemoteInterface(psiClass)) return true + + val baseClass = getTargetRemoteClass(element.project, uClass) ?: return true + + val searchableClass = targetMethod.containingClass + if (!isInheritorOrSelf(searchableClass, baseClass, true) + && !isInheritorOrSelf(baseClass, searchableClass, true)) { + return true + } + + val reference = PsiReferenceBase.createSelfReference( + element, + TextRange(offsetInElement, offsetInElement + methodName.length), + targetMethod + ) + + return consumer.process(reference) + } + } + ) + } +} + +internal fun isRemoteInterface(clazz: PsiClass?): Boolean { + return clazz != null && clazz.isInterface && AnnotationUtil.isAnnotated(clazz, REMOTE_ANNOTATION_FQN, 0) +} + +internal fun getTargetRemoteClass(project: Project, uClass: UClass): PsiClass? { + val targetClassFqn = uClass.uAnnotations + .find { it.qualifiedName == REMOTE_ANNOTATION_FQN } + ?.findAttributeValue("value") + ?.evaluateString() + ?: return null + + return JavaPsiFacade.getInstance(project) + .findClass(targetClassFqn, allScope(project)) +} \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/driver/RemoteUseScopeEnlarger.kt b/plugins/devkit/devkit-core/src/driver/RemoteUseScopeEnlarger.kt new file mode 100644 index 000000000000..5aadc1f567ae --- /dev/null +++ b/plugins/devkit/devkit-core/src/driver/RemoteUseScopeEnlarger.kt @@ -0,0 +1,35 @@ +package org.jetbrains.idea.devkit.driver + +import com.intellij.java.library.JavaLibraryUtil +import com.intellij.lang.jvm.JvmModifier +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.search.GlobalSearchScope.allScope +import com.intellij.psi.search.SearchScope +import com.intellij.psi.search.UseScopeEnlarger + +internal const val REMOTE_ANNOTATION_FQN = "com.intellij.driver.client.Remote" + +internal class RemoteUseScopeEnlarger : UseScopeEnlarger() { + override fun getAdditionalUseScope(element: PsiElement): SearchScope? { + if (element !is PsiClass && element !is PsiMethod) return null + + if (element is PsiMethod + && (element.hasModifier(JvmModifier.PRIVATE) + || element.hasModifier(JvmModifier.PACKAGE_LOCAL) + || element.hasModifier(JvmModifier.PROTECTED)) + ) { + return null + } + + val project = element.project + if (!JavaLibraryUtil.hasLibraryClass(project, REMOTE_ANNOTATION_FQN)) return null + + val remoteClass = JavaPsiFacade.getInstance(project) + .findClass(REMOTE_ANNOTATION_FQN, allScope(project)) ?: return null + + return remoteClass.useScope + } +} \ No newline at end of file