Support PluginMainServiceNotConfiguredInspection and ConfigurePluginMainServiceFix

This commit is contained in:
Him188 2020-11-10 13:59:41 +08:00
parent e37ac17b82
commit 0c3a4c735d
10 changed files with 172 additions and 25 deletions

View File

@ -19,10 +19,11 @@ import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.EnumValue
///////////////////////////////////////////////////////////////////////////
// Serializer
// OTHERS
///////////////////////////////////////////////////////////////////////////
val SERIALIZABLE_FQ_NAME = FqName("kotlinx.serialization.Serializable")
val AUTO_SERVICE = FqName("com.google.auto.service.AutoService")
///////////////////////////////////////////////////////////////////////////

View File

@ -32,6 +32,12 @@
<codeInsight.lineMarkerProvider language="kotlin"
implementationClass="net.mamoe.mirai.console.intellij.line.marker.CommandDeclarationLineMarkerProvider"/>
<localInspection groupPath="Mirai console" language="kotlin" shortName="PluginMainServiceNotConfigured"
bundle="messages.InspectionGadgetsBundle"
key="plugin.service.not.configured.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.plugin.service.issues" enabledByDefault="true" level="WARNING"
implementationClass="net.mamoe.mirai.console.intellij.diagnostics.PluginMainServiceNotConfiguredInspection"/>
<!--
<intentionAction>
<className>net.mamoe.mirai.console.intellij.diagnostics.fix.AbuseYellowIntention</className>

View File

@ -0,0 +1,9 @@
#
# Copyright 2019-2020 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/master/LICENSE
#
plugin.service.not.configured.display.name=Plugin main not configured

View File

@ -0,0 +1,9 @@
#
# Copyright 2019-2020 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/master/LICENSE
#
group.names.plugin.service.issues=Plugin main class issues

View File

@ -29,6 +29,3 @@ object MyPluginMain : KotlinPlugin(
}
}
val y = "傻逼 yellow"

View File

@ -0,0 +1,77 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import net.mamoe.mirai.console.compiler.common.resolve.AUTO_SERVICE
import net.mamoe.mirai.console.intellij.diagnostics.fix.ConfigurePluginMainServiceFix
import net.mamoe.mirai.console.intellij.resolve.hasAnnotation
import org.jetbrains.kotlin.idea.debugger.readAction
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.idea.util.rootManager
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.referenceExpressionVisitor
import java.util.*
/*
private val bundle by lazy {
BundleUtil.loadLanguageBundle(PluginMainServiceNotConfiguredInspection::class.java.classLoader, "messages.InspectionGadgetsBundle")!!
}*/
class PluginMainServiceNotConfiguredInspection : AbstractKotlinInspection() {
companion object {
private val SERVICE_FILE_NAMES = arrayOf(
"net.mamoe.mirai.console.plugin.jvm.JvmPlugin",
"net.mamoe.mirai.console.plugin.jvm.KotlinPlugin",
"net.mamoe.mirai.console.plugin.jvm.JavaPlugin",
)
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return referenceExpressionVisitor visitor@{ referenceExpr ->
val ktClass = referenceExpr.resolveMiraiPluginDeclaration() ?: return@visitor
val fqName = ktClass.fqName?.asString() ?: return@visitor
val found = isServiceConfiguredWithAutoService(ktClass)
|| isServiceConfiguredWithResource(ktClass, fqName)
if (!found) {
holder.registerProblem(
ktClass.nameIdentifier ?: ktClass.identifyingElement ?: ktClass,
"插件主类服务未配置",
ProblemHighlightType.WARNING,
ConfigurePluginMainServiceFix(ktClass)
)
}
}
}
private fun isServiceConfiguredWithAutoService(
ktClass: KtClassOrObject,
): Boolean = ktClass.hasAnnotation(AUTO_SERVICE)
private fun isServiceConfiguredWithResource(
ktClass: KtClassOrObject,
fqName: String,
): Boolean {
val sourceRoots = ktClass.module?.rootManager?.sourceRoots ?: return false
val services = sourceRoots.asSequence().flatMap { file ->
SERVICE_FILE_NAMES.asSequence().mapNotNull { serviceFileName ->
file.findFileByRelativePath("META-INF/services/$serviceFileName")
}
}
return services.any { serviceFile ->
serviceFile.readAction { f -> f.inputStream.bufferedReader().use { it.readLine() }.trim() == fqName }
}
}
}

View File

@ -9,16 +9,19 @@
package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.util.castSafelyTo
import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME
import net.mamoe.mirai.console.compiler.common.resolve.parents
import net.mamoe.mirai.console.intellij.resolve.allSuperNames
import net.mamoe.mirai.console.intellij.resolve.getResolvedCall
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
@ -40,4 +43,13 @@ fun KtTypeReference.isReferencing(fqName: FqName): Boolean {
val KtTypeReference.referencedUserType: KtUserType? get() = this.typeElement.castOrNull()
fun KtTypeReference.resolveReferencedType() = referencedUserType?.referenceExpression?.mainReference?.resolve()
fun KtTypeReference.resolveReferencedType() = referencedUserType?.referenceExpression?.mainReference?.resolve()
fun KtReferenceExpression.resolveMiraiPluginDeclaration(): KtClassOrObject? {
val main =
parents.filterIsInstance<KtClassOrObject>().firstOrNull() ?: return null
val kotlinPluginClass =
resolve().castSafelyTo<KtConstructor<*>>()?.parent?.castSafelyTo<KtClass>() ?: return null
if (kotlinPluginClass.allSuperNames.none { it == PLUGIN_FQ_NAME }) return null
return main
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.intellij.diagnostics.fix
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.rootManager
import com.intellij.psi.PsiFile
import com.intellij.testFramework.writeChild
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.psi.KtClassOrObject
class ConfigurePluginMainServiceFix(
element: KtClassOrObject,
) : KotlinCrossLanguageQuickFixAction<KtClassOrObject>(element), KotlinUniversalQuickFix, LocalQuickFix {
override fun getFamilyName(): String = "Mirai Console"
override fun getText(): String = "配置插件主类服务"
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
val elementFqName = element?.fqName ?: return
val sourceRoots = file.module?.rootManager?.sourceRoots ?: return
val sourceRoot = sourceRoots.find { it.name.endsWith("resources") }
?: sourceRoots.find { it.name.contains("resources") }
?: sourceRoots.last()
project.executeWriteCommand(name) {
sourceRoot.writeChild("META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin", elementFqName.asString().toByteArray())
}
}
}

View File

@ -15,27 +15,16 @@ import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.psi.PsiElement
import com.intellij.util.castSafelyTo
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME
import net.mamoe.mirai.console.compiler.common.resolve.parents
import net.mamoe.mirai.console.intellij.Icons
import net.mamoe.mirai.console.intellij.resolve.allSuperNames
import net.mamoe.mirai.console.intellij.diagnostics.resolveMiraiPluginDeclaration
import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtConstructor
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtReferenceExpression
class PluginMainLineMarkerProvider : LineMarkerProvider {
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
if (element !is KtReferenceExpression) return null
val objectDeclaration =
element.parents.filterIsInstance<KtObjectDeclaration>().firstOrNull() ?: return null
val kotlinPluginClass =
element.resolve().castSafelyTo<KtConstructor<*>>()?.parent?.castSafelyTo<KtClass>() ?: return null
if (kotlinPluginClass.allSuperNames.none { it == PLUGIN_FQ_NAME }) return null
return Info(getElementForLineMark(objectDeclaration))
val main = element.resolveMiraiPluginDeclaration() ?: return null
return Info(getElementForLineMark(main))
}
@Suppress("DEPRECATION")

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.console.intellij.resolve
import com.intellij.psi.PsiDeclarationStatement
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parentsWithSelf
import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME
import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME
@ -19,9 +20,9 @@ import net.mamoe.mirai.console.compiler.common.resolve.findParent
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
@ -32,6 +33,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@ -53,7 +55,8 @@ val KtPureClassOrObject.allSuperTypes: Sequence<KtSuperTypeListEntry>
get() = sequence {
yieldAll(superTypeListEntries)
for (list in superTypeListEntries.asSequence()) {
yieldAll((list.typeAsUserType?.referenceExpression?.resolve() as? KtClass)?.allSuperTypes.orEmpty())
yieldAll((list.typeAsUserType?.referenceExpression?.resolve()?.parentsWithSelf?.filterIsInstance<KtClass>()
?.firstOrNull())?.allSuperTypes.orEmpty())
}
}
@ -145,7 +148,7 @@ fun KtExpression.resolveStringConstantValues(): Sequence<String> {
is KtNameReferenceExpression -> {
when (val reference = references.firstIsInstance<KtSimpleNameReference>().resolve()) {
is KtDeclaration -> {
val descriptor = reference.descriptor.castOrNull<VariableDescriptor>() ?: return emptySequence()
val descriptor = reference.resolveToDescriptorIfAny(BodyResolveMode.FULL).castOrNull<VariableDescriptor>() ?: return emptySequence()
val compileTimeConstant = descriptor.compileTimeInitializer ?: return emptySequence()
return compileTimeConstant.selfOrChildrenConstantStrings()
}