New inspection: ILLEGAL_COMMAND_DECLARATION_RECEIVER, close #174

This commit is contained in:
Him188 2020-11-29 15:05:39 +08:00
parent 062227e072
commit bc290f63bb
7 changed files with 101 additions and 1 deletions

View File

@ -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<PsiElement, String, String>(ERROR)
// @JvmField
// val INAPPLICABLE_COMMAND_ANNOTATION = create<PsiElement, String>(ERROR)
@JvmField
val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR)
@Suppress("ObjectPropertyName", "unused")
@JvmField
@Deprecated("", level = DeprecationLevel.ERROR)

View File

@ -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

View File

@ -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

View File

@ -17,7 +17,7 @@ object MySimpleCommand000 : SimpleCommand(
}
object DataTest : AutoSavePluginConfig("data") {
val pp by value<NoDefaultValue>(NoDefaultValue(1))
val pp by value(NoDefaultValue(1))
}
@Serializable

View File

@ -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())
}
}

View File

@ -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
}
}
}

View File

@ -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<KtSuperTypeListEntry>
}
}
val PsiClass.allSuperTypes: Sequence<PsiClass>
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<FqName> get() = allSuperTypes.mapNotNull { it.getKotlinFqName() }
val PsiClass.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { clazz -> clazz.qualifiedName?.let { FqName(it) } }
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@kotlin.internal.LowPriorityInOverloadResolution
val PsiElement.allSuperNames: Sequence<FqName>
get() {
return when (this) {
is KtClassOrObject -> allSuperNames
is PsiClass -> allSuperNames
else -> emptySequence()
}
}
fun getElementForLineMark(callElement: PsiElement): PsiElement =
when (callElement) {