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