[microservices] IDEA-353802 The ability to provide custom isMain

This allows us to remove kotlin-specific checks for the main method from the common side

GitOrigin-RevId: 4a3fca78220bdc0e67abfe96c5ea219aa350027e
This commit is contained in:
Marat Dinmukhametov
2024-05-28 18:40:35 +03:00
committed by intellij-monorepo-bot
parent 1a50519778
commit cfe75d3aa7
4 changed files with 97 additions and 69 deletions

View File

@@ -4,8 +4,11 @@ package com.intellij.codeInsight.runner;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.PossiblyDumbAware;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.PsiMethodUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -46,4 +49,17 @@ public interface JavaMainMethodProvider extends PossiblyDumbAware {
*/
@Contract(pure = true)
@Nullable PsiMethod findMainInClass(@NotNull PsiClass clazz);
@Contract(pure = true)
default @Nullable String getMainClassName(@NotNull PsiClass clazz) {
return ClassUtil.getJVMClassName(clazz);
}
@Contract(pure = true)
default boolean isMain(@NotNull PsiElement psiElement) {
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class);
if (psiMethod == null) return false;
return "main".equals(psiMethod.getName()) && PsiMethodUtil.isMainMethod(psiMethod);
}
}

View File

@@ -28,14 +28,12 @@ public final class PsiMethodUtil {
@Nullable
public static PsiMethod findMainMethod(final PsiClass aClass) {
List<JavaMainMethodProvider> extensionList = JavaMainMethodProvider.EP_NAME.getExtensionList();
DumbService dumbService = DumbService.getInstance(aClass.getProject());
for (JavaMainMethodProvider provider : dumbService.filterByDumbAwareness(extensionList)) {
if (provider.isApplicable(aClass)) {
return provider.findMainInClass(aClass);
}
JavaMainMethodProvider mainMethodProvider = getApplicableMainMethodProvider(aClass);
if (mainMethodProvider != null) {
return mainMethodProvider.findMainInClass(aClass);
}
DumbService dumbService = DumbService.getInstance(aClass.getProject());
try {
return dumbService.computeWithAlternativeResolveEnabled((ThrowableComputable<PsiMethod, Throwable>)() -> {
final PsiMethod[] mainMethods = aClass.findMethodsByName("main", true);
@@ -132,13 +130,12 @@ public final class PsiMethodUtil {
}
public static boolean hasMainMethod(final PsiClass psiClass) {
DumbService dumbService = DumbService.getInstance(psiClass.getProject());
for (JavaMainMethodProvider provider : dumbService.filterByDumbAwareness(JavaMainMethodProvider.EP_NAME.getExtensionList())) {
if (provider.isApplicable(psiClass)) {
return provider.hasMainMethod(psiClass);
}
JavaMainMethodProvider mainMethodProvider = getApplicableMainMethodProvider(psiClass);
if (mainMethodProvider != null) {
return mainMethodProvider.hasMainMethod(psiClass);
}
DumbService dumbService = DumbService.getInstance(psiClass.getProject());
try {
return dumbService.computeWithAlternativeResolveEnabled((ThrowableComputable<Boolean, Throwable>)() -> {
final PsiMethod[] mainMethods = psiClass.findMethodsByName("main", true);
@@ -150,9 +147,49 @@ public final class PsiMethodUtil {
}
}
public static boolean isMainMethodWithProvider(@NotNull PsiClass psiClass, @NotNull PsiElement psiElement) {
JavaMainMethodProvider mainMethodProvider = getApplicableMainMethodProvider(psiClass);
if (mainMethodProvider != null) {
return mainMethodProvider.isMain(psiElement);
}
DumbService dumbService = DumbService.getInstance(psiElement.getProject());
try {
return dumbService.computeWithAlternativeResolveEnabled((ThrowableComputable<Boolean, Throwable>)() -> {
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class);
if (psiMethod == null) return false;
return "main".equals(psiMethod.getName()) && isMainMethod(psiMethod);
});
}
catch (IndexNotReadyException e) {
return false;
}
}
@Nullable
public static String getMainJVMClassName(@NotNull PsiClass psiClass) {
JavaMainMethodProvider mainMethodProvider = getApplicableMainMethodProvider(psiClass);
if (mainMethodProvider != null) {
return mainMethodProvider.getMainClassName(psiClass);
}
return ClassUtil.getJVMClassName(psiClass);
}
@Nullable
public static PsiMethod findMainInClass(final PsiClass aClass) {
if (!MAIN_CLASS.value(aClass)) return null;
return findMainMethod(aClass);
}
@Nullable
private static JavaMainMethodProvider getApplicableMainMethodProvider(@NotNull PsiClass aClass) {
DumbService dumbService = DumbService.getInstance(aClass.getProject());
List<JavaMainMethodProvider> javaMainMethodProviders =
dumbService.filterByDumbAwareness(JavaMainMethodProvider.EP_NAME.getExtensionList());
return ContainerUtil.find(javaMainMethodProviders, provider -> provider.isApplicable(aClass));
}
}

View File

@@ -52,4 +52,21 @@ class KotlinMainMethodProvider : JavaMainMethodProvider {
val classOrObject = lightClassBase?.kotlinOrigin ?: return@runReadAction null
mainFunctionDetector.findMain(classOrObject)?.toLightMethods()?.firstOrNull()
}
override fun getMainClassName(clazz: PsiClass): String? {
return when (clazz) {
is KtLightClassForFacadeBase -> clazz.facadeClassFqName.asString()
is KtLightClassBase -> {
val classOrObject = clazz.kotlinOrigin ?: return null
KotlinRunConfigurationProducer.getMainClassJvmName(classOrObject)
}
else -> null
}
}
override fun isMain(psiElement: PsiElement): Boolean {
val ktNamedFunction = psiElement.parentOfType<KtNamedFunction>() ?: return false
val mainFunctionDetector = KotlinMainFunctionDetector.getInstanceDumbAware(psiElement.project)
return runReadAction { mainFunctionDetector.isMain(ktNamedFunction) }
}
}

View File

@@ -18,10 +18,9 @@
package org.jetbrains.uast
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiMethodUtil
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.visitor.UastVisitor
/**
@@ -208,65 +207,24 @@ fun UElement.asRecursiveLogString(render: (UElement) -> String = { it.asLogStrin
return stringBuilder.toString()
}
@Deprecated(
message = "This method is deprecated, use PsiMethodUtil.findMainInClass instead",
replaceWith = ReplaceWith("uClass?.let { PsiMethodUtil.findMainInClass(it.javaPsi) }")
)
fun findMainInClass(uClass: UClass?): PsiMethod? {
val javaPsi = uClass?.javaPsi ?: return null
PsiMethodUtil.findMainInClass(javaPsi)?.let { return it }
return null
}
/**
* @return method's containing class if the given method is main method,
* or companion object's containing class if the given method is main method annotated with [kotlin.jvm.JvmStatic] in companion object,
* otherwise *null*.
*/
@Deprecated(message = "Use PsiMethodUtil.isMainMethodWithProvider instead")
fun getMainMethodClass(uMainMethod: UMethod): PsiClass? {
if ("main" != uMainMethod.name) return null
val containingClass = uMainMethod.uastParent as? UClass ?: return null
val mainMethod = uMainMethod.javaPsi
if (PsiMethodUtil.isMainMethod(mainMethod)) return containingClass.javaPsi
//a workaround for KT-33956
if (isKotlinParameterlessMain(mainMethod)) return containingClass.javaPsi
if (isKotlinSuspendMain(uMainMethod)) return containingClass.javaPsi
// Check for @JvmStatic main method in companion object
val parentClassForCompanionObject = (containingClass.uastParent as? UClass)?.javaPsi ?: return null
val mainInClass = PsiMethodUtil.findMainInClass(parentClassForCompanionObject)
if (mainMethod.manager.areElementsEquivalent(mainMethod, mainInClass)) {
return parentClassForCompanionObject
}
return null
}
private fun isKotlinParameterlessMain(mainMethod: PsiMethod) =
mainMethod.language.id == "kotlin"
&& mainMethod.parameterList.parameters.isEmpty()
&& PsiTypes.voidType() == mainMethod.returnType
&& mainMethod.hasModifierProperty(PsiModifier.STATIC)
private fun isKotlinSuspendMain(uMainMethod: UMethod): Boolean {
val sourcePsi = uMainMethod.sourcePsi ?: return false
if (sourcePsi.language.id != "kotlin") return false
val child = sourcePsi.children.firstOrNull() ?: return false
if (!SyntaxTraverser.psiTraverser(child).any { it is LeafPsiElement && it.textMatches("suspend") }) return false
val method = uMainMethod.javaPsi
val parameters: Array<PsiParameter> = method.parameterList.parameters
if (parameters.size > 2) return false
// suspend main method has additional parameter kotlin.coroutines.Continuation but it could be seen as java.lang.Object
// on the PSI level, so we don't check it directly
if (parameters.size == 2) {
val argsType = parameters[0].type as? PsiArrayType ?: return false
if (!argsType.componentType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) return false
}
return method.hasModifierProperty(PsiModifier.STATIC) && method.returnType?.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) == true
}
@ApiStatus.Experimental
fun findMainInClass(uClass: UClass?): PsiMethod? {
val javaPsi = uClass?.javaPsi ?: return null
PsiMethodUtil.findMainInClass(javaPsi)?.let { return it }
//a workaround for KT-33956
javaPsi.methods.find(::isKotlinParameterlessMain)?.let { return it }
uClass.methods.find(::isKotlinSuspendMain)?.javaPsi?.let { return it }
return null
val containingClass = uMainMethod.getContainingUClass() ?: return null
return containingClass.javaPsi.takeIf { psiClass -> PsiMethodUtil.isMainMethodWithProvider(psiClass, uMainMethod.javaPsi) }
}