[devkit] Implement Go to implementation for @Remote methods and find usages in tests for production methods

GitOrigin-RevId: a3b28d0f93b8aa807f8b2b3f459ab56d69e49203
This commit is contained in:
Yuriy Artamonov
2024-03-17 17:25:54 +00:00
committed by intellij-monorepo-bot
parent dcd7792c72
commit 8e9ccf6091
4 changed files with 260 additions and 2 deletions
@@ -60,9 +60,9 @@
<fileBasedIndex implementation="org.jetbrains.idea.devkit.dom.index.PluginIdDependenciesIndex"/>
<fileBasedIndex implementation="org.jetbrains.idea.devkit.dom.index.ExtensionPointIndex"/>
<fileBasedIndex implementation="org.jetbrains.idea.devkit.dom.index.ExtensionPointClassIndex"/>
<dom.fileMetaData implementation="org.jetbrains.idea.devkit.dom.impl.productModules.ProductModulesDomFileDescription"
rootTagName="product-modules" />
rootTagName="product-modules"/>
<dom.fileMetaData implementation="org.jetbrains.idea.devkit.dom.liveTemplates.TemplateSetDescription" rootTagName="templateSet"/>
<editorTabTitleProvider implementation="org.jetbrains.idea.devkit.dom.ide.PluginDescriptorEditorTabTitleProvider"/>
@@ -86,6 +86,13 @@
<completion.contributor implementationClass="org.jetbrains.idea.devkit.completion.ExtensionOrderKeywordCompletionContributor"
language="XML"/>
<useScopeEnlarger implementation="org.jetbrains.idea.devkit.driver.RemoteUseScopeEnlarger"/>
<methodReferencesSearch implementation="org.jetbrains.idea.devkit.driver.RemoteMethodReferenceSearcher"/>
<overridingMethodsSearch implementation="org.jetbrains.idea.devkit.driver.RemoteMethodImplementationSearcher"/>
<allOverridingMethodsSearch implementation="org.jetbrains.idea.devkit.driver.RemoteMethodAllImplementationSearcher"/>
<directClassInheritorsSearch implementation="org.jetbrains.idea.devkit.driver.RemoteInterfaceDirectImplementationSearcher"/>
<classInheritorsSearch implementation="org.jetbrains.idea.devkit.driver.RemoteInterfaceImplementationSearcher"/>
<localInspection language="XML" shortName="PluginXmlValidity" applyToDialects="false"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path" groupKey="inspections.group.descriptor"
@@ -0,0 +1,100 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.driver
import com.intellij.lang.jvm.JvmModifier
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.util.Couple
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.intellij.psi.search.searches.AllOverridingMethodsSearch
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.search.searches.DirectClassInheritorsSearch
import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.util.Processor
import com.intellij.util.QueryExecutor
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.getContainingUClass
import org.jetbrains.uast.toUElementOfType
internal class RemoteMethodImplementationSearcher : QueryExecutor<PsiMethod, OverridingMethodsSearch.SearchParameters> {
override fun execute(queryParameters: OverridingMethodsSearch.SearchParameters, consumer: Processor<in PsiMethod>): Boolean {
return ReadAction.compute<Boolean, Throwable> {
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<Couple<PsiMethod>, AllOverridingMethodsSearch.SearchParameters> {
override fun execute(queryParameters: AllOverridingMethodsSearch.SearchParameters, consumer: Processor<in Couple<PsiMethod>>): Boolean {
return ReadAction.compute<Boolean, Throwable> {
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<PsiMethod> {
if (!psiMethod.isValid) return emptyList()
val uClass = psiMethod.toUElementOfType<UMethod>()?.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<PsiClass, DirectClassInheritorsSearch.SearchParameters> {
override fun execute(queryParameters: DirectClassInheritorsSearch.SearchParameters, consumer: Processor<in PsiClass>): Boolean {
return processRemoteTargetClass(queryParameters.classToProcess, consumer)
}
}
internal class RemoteInterfaceImplementationSearcher : QueryExecutor<PsiClass, ClassInheritorsSearch.SearchParameters> {
override fun execute(queryParameters: ClassInheritorsSearch.SearchParameters, consumer: Processor<in PsiClass>): Boolean {
return processRemoteTargetClass(queryParameters.classToProcess, consumer)
}
}
private fun processRemoteTargetClass(classToProcess: PsiClass,
consumer: Processor<in PsiClass>): Boolean {
return ReadAction.compute<Boolean, Throwable> {
if (isRemoteInterface(classToProcess)) {
val uClass = classToProcess.toUElementOfType<UClass>() ?: return@compute true
val targetClass = getTargetRemoteClass(classToProcess.project, uClass)
if (targetClass != null) {
if (!consumer.process(targetClass)) {
return@compute false
}
}
}
return@compute true
}
}
@@ -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<PsiReference, MethodReferencesSearch.SearchParameters>() {
override fun processQuery(
queryParameters: MethodReferencesSearch.SearchParameters,
consumer: Processor<in PsiReference>
) {
val targetMethod = queryParameters.method
val methodName = ReadAction.compute<String, Throwable> { targetMethod.name }
queryParameters.optimizer.searchWord(
methodName,
ReadAction.compute<SearchScope, RuntimeException> {
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<in PsiReference>
): 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))
}
@@ -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
}
}