diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt index 53df01f1d..a9b5b9ada 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create import org.jetbrains.kotlin.diagnostics.Errors @@ -17,6 +18,7 @@ import org.jetbrains.kotlin.diagnostics.Severity.ERROR import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtTypeProjection +import org.jetbrains.kotlin.psi.KtTypeReference /** * 如何增加一个错误: @@ -54,6 +56,12 @@ object MiraiConsoleErrors { @JvmField val ILLEGAL_VERSION_REQUIREMENT = create(ERROR) +// @JvmField +// val INAPPLICABLE_COMMAND_ANNOTATION = create(ERROR) + + @JvmField + val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create(ERROR) + @Suppress("ObjectPropertyName", "unused") @JvmField @Deprecated("", level = DeprecationLevel.ERROR) diff --git a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt index 73e60cd4b..3d808000b 100644 --- a/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_REGISTER_USE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID @@ -95,6 +96,17 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { Renderers.STRING, Renderers.STRING ) + + put( + ILLEGAL_COMMAND_DECLARATION_RECEIVER, + "指令函数的接收者参数必须为 CommandSender 及其子类或无接收者.", + ) + +// put( +// INAPPLICABLE_COMMAND_ANNOTATION, +// "''{0}'' 无法在顶层函数使用.", +// Renderers.STRING, +// ) } override fun getMap() = MAP diff --git a/tools/compiler-common/src/resolve/resolveTypes.kt b/tools/compiler-common/src/resolve/resolveTypes.kt index c8d5bce00..7d4fb7c07 100644 --- a/tools/compiler-common/src/resolve/resolveTypes.kt +++ b/tools/compiler-common/src/resolve/resolveTypes.kt @@ -32,6 +32,7 @@ val AUTO_SERVICE = FqName("com.google.auto.service.AutoService") val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") +val COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.CommandSender") /////////////////////////////////////////////////////////////////////////// // Plugin diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt index 85199308a..61fc4a46e 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt @@ -17,7 +17,7 @@ object MySimpleCommand000 : SimpleCommand( } object DataTest : AutoSavePluginConfig("data") { - val pp by value(NoDefaultValue(1)) + val pp by value(NoDefaultValue(1)) } @Serializable diff --git a/tools/intellij-plugin/src/IDEContainerContributor.kt b/tools/intellij-plugin/src/IDEContainerContributor.kt index 00b331fc7..c6b8d1f59 100644 --- a/tools/intellij-plugin/src/IDEContainerContributor.kt +++ b/tools/intellij-plugin/src/IDEContainerContributor.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.intellij +import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker import org.jetbrains.kotlin.container.StorageComponentContainer @@ -24,5 +25,6 @@ class IDEContainerContributor : StorageComponentContainerContributor { ) { container.useInstance(ContextualParametersChecker()) container.useInstance(PluginDataValuesChecker()) + container.useInstance(CommandDeclarationChecker()) } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt b/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt new file mode 100644 index 000000000..f840fc5c4 --- /dev/null +++ b/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt @@ -0,0 +1,44 @@ +package net.mamoe.mirai.console.intellij.diagnostics + +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER +import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME +import net.mamoe.mirai.console.intellij.resolve.hasSuperType +import net.mamoe.mirai.console.intellij.resolve.isCompositeCommandSubCommand +import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandler +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext + +class CommandDeclarationChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (declaration !is KtNamedFunction) return + + // exclusive checks + when { + declaration.isSimpleCommandHandler() -> { + } + + declaration.isCompositeCommandSubCommand() -> { + } + else -> return + } + + // common checks + checkCommandReceiverParameter(declaration)?.let { context.report(it) } + } + + companion object { + fun checkCommandReceiverParameter(declaration: KtNamedFunction): Diagnostic? { + val receiverTypeRef = declaration.receiverTypeReference ?: return null // no receiver, accept. + val receiver = receiverTypeRef.resolveReferencedType() ?: return null // unresolved type + if (!receiver.hasSuperType(COMMAND_SENDER_FQ_NAME)) { + return ILLEGAL_COMMAND_DECLARATION_RECEIVER.on(receiverTypeRef) + } + + return null + } + } +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/resolve/resolveIdea.kt b/tools/intellij-plugin/src/resolve/resolveIdea.kt index 3cb91405f..e4c7a659a 100644 --- a/tools/intellij-plugin/src/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/resolve/resolveIdea.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.intellij.resolve +import com.intellij.psi.PsiClass import com.intellij.psi.PsiDeclarationStatement import com.intellij.psi.PsiElement import com.intellij.psi.util.parentsWithSelf @@ -60,6 +61,19 @@ val KtPureClassOrObject.allSuperTypes: Sequence } } +val PsiClass.allSuperTypes: Sequence + get() = sequence { + interfaces.forEach { + yield(it) + yieldAll(it.allSuperTypes) + } + val superClass = superClass + if (superClass != null) { + yield(superClass) + yieldAll(superClass.allSuperTypes) + } + } + fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { val reference = typeReference if (reference != null) { @@ -71,7 +85,26 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { return null } +fun KtClassOrObject.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) +fun KtClass.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +@kotlin.internal.LowPriorityInOverloadResolution +fun PsiElement.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) + val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } +val PsiClass.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { clazz -> clazz.qualifiedName?.let { FqName(it) } } + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +@kotlin.internal.LowPriorityInOverloadResolution +val PsiElement.allSuperNames: Sequence + get() { + return when (this) { + is KtClassOrObject -> allSuperNames + is PsiClass -> allSuperNames + else -> emptySequence() + } + } fun getElementForLineMark(callElement: PsiElement): PsiElement = when (callElement) {