mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
Add USING_DERIVED_XXX_TYPE diagnostic and ConvertToXXXFix for derived collection type usages in PluginData delegation. fix #278
This commit is contained in:
parent
da3d7a7e19
commit
6d02764e2e
@ -37,6 +37,21 @@ object MiraiConsoleErrors {
|
||||
@JvmField
|
||||
val NOT_CONSTRUCTABLE_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val USING_DERIVED_MUTABLE_MAP_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val USING_DERIVED_MAP_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val USING_DERIVED_LIST_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val USING_DERIVED_MUTABLE_LIST_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val USING_DERIVED_CONCURRENT_MAP_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val UNSERIALIZABLE_TYPE = create<PsiElement, ClassDescriptor>(ERROR)
|
||||
|
||||
|
@ -22,6 +22,11 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NO
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_LIST_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_MAP_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_MUTABLE_LIST_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_MUTABLE_MAP_TYPE
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
|
||||
@ -114,6 +119,41 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
|
||||
"在 ReadOnlyPluginData 中不可定义 'var' by value",
|
||||
)
|
||||
|
||||
put(
|
||||
USING_DERIVED_MAP_TYPE,
|
||||
"使用 'Map' 的派生类型 '{1}'.",
|
||||
Renderers.EMPTY,
|
||||
Renderers.STRING,
|
||||
)
|
||||
|
||||
put(
|
||||
USING_DERIVED_MUTABLE_MAP_TYPE,
|
||||
"使用 'MutableMap' 的派生类型 '{1}'.",
|
||||
Renderers.EMPTY,
|
||||
Renderers.STRING,
|
||||
)
|
||||
|
||||
put(
|
||||
USING_DERIVED_LIST_TYPE,
|
||||
"使用 'List' 的派生类型 '{1}'.",
|
||||
Renderers.EMPTY,
|
||||
Renderers.STRING,
|
||||
)
|
||||
|
||||
put(
|
||||
USING_DERIVED_MUTABLE_LIST_TYPE,
|
||||
"使用 'MutableList' 的派生类型 '{1}'.",
|
||||
Renderers.EMPTY,
|
||||
Renderers.STRING,
|
||||
)
|
||||
|
||||
put(
|
||||
USING_DERIVED_CONCURRENT_MAP_TYPE,
|
||||
"使用 'ConcurrentMap' 的派生类型 '{1}'.",
|
||||
Renderers.EMPTY,
|
||||
Renderers.STRING,
|
||||
)
|
||||
|
||||
// put(
|
||||
// INAPPLICABLE_COMMAND_ANNOTATION,
|
||||
// "''{0}'' 无法在顶层函数使用.",
|
||||
|
@ -8,6 +8,10 @@ plugins {
|
||||
group = "org.example"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package org.example.myplugin
|
||||
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import org.example.myplugin.DataTest1.provideDelegate
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
||||
object UsingDerivedMap : AutoSavePluginConfig("data") {
|
||||
var p1 by value<MutableMap<String, String>>()
|
||||
var p2 by value<Map<String, String>>()
|
||||
|
||||
var p4 by value<HashMap<String, String>>()
|
||||
var p3 by value<ConcurrentHashMap<String, String>>()
|
||||
|
||||
var p5 by value<ArrayList<String>>()
|
||||
var p6 by value<AbstractList<String>>()
|
||||
}
|
@ -11,9 +11,7 @@ package net.mamoe.mirai.console.intellij
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.fix.AddSerializerFix
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.fix.ConvertToValFix
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.fix.ProvideDefaultValueFix
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.fix.*
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor
|
||||
@ -33,5 +31,11 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
MiraiConsoleErrors.UNSERIALIZABLE_TYPE.registerFactory(AddSerializerFix)
|
||||
MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.registerFactory(ProvideDefaultValueFix)
|
||||
MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.registerFactory(ConvertToValFix)
|
||||
|
||||
MiraiConsoleErrors.USING_DERIVED_MAP_TYPE.registerFactory(ConvertToMapFix)
|
||||
MiraiConsoleErrors.USING_DERIVED_MUTABLE_MAP_TYPE.registerFactory(ConvertToMutableMapFix)
|
||||
MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE.registerFactory(ConvertToConcurrentMapFix)
|
||||
MiraiConsoleErrors.USING_DERIVED_LIST_TYPE.registerFactory(ConvertToListFix)
|
||||
MiraiConsoleErrors.USING_DERIVED_MUTABLE_LIST_TYPE.registerFactory(ConvertToMutableListFix)
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
||||
import org.jetbrains.kotlin.idea.project.builtIns
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
|
||||
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@ -28,9 +29,9 @@ import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
|
||||
|
||||
class PluginDataValuesChecker : DeclarationChecker {
|
||||
/**
|
||||
@ -42,7 +43,7 @@ class PluginDataValuesChecker : DeclarationChecker {
|
||||
//println(declaration::class.qualifiedName + "\t:" + declaration.text.take(10))
|
||||
|
||||
if (declaration is KtProperty) {
|
||||
if (checkReadOnly(declaration, context)) return // it reports an error on property so no need to check further
|
||||
checkReadOnly(declaration, context)
|
||||
}
|
||||
|
||||
val calls = declaration.bodyCalls(bindingContext) ?: return
|
||||
@ -67,7 +68,7 @@ class PluginDataValuesChecker : DeclarationChecker {
|
||||
|| receiverTypeReference?.hasSuperType(READ_ONLY_PLUGIN_DATA_FQ_NAME) == true // extension
|
||||
}
|
||||
|
||||
private fun checkReadOnly(property: KtProperty, context: DeclarationCheckerContext): Boolean {
|
||||
private fun checkReadOnly(property: KtProperty, context: DeclarationCheckerContext) {
|
||||
// first parent is KtPropertyDelegate, next is KtProperty
|
||||
|
||||
if (property.isVar // var
|
||||
@ -75,10 +76,7 @@ class PluginDataValuesChecker : DeclarationChecker {
|
||||
&& property.isInsideOrExtensionOfReadOnlyPluginData() // extensionReceiver is ReadOnlyPluginData or null
|
||||
) {
|
||||
context.report(MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.on(property.valOrVarKeyword))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkConstructableAndSerializable(call: ResolvedCall<out CallableDescriptor>, expr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
@ -90,19 +88,48 @@ class PluginDataValuesChecker : DeclarationChecker {
|
||||
) {
|
||||
|
||||
checkConstructableAndSerializable(kotlinType, expr, context)
|
||||
checkFixType(kotlinType, expr, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFixType(type: KotlinType, callExpr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
val inspectionTarget = retrieveInspectionTarget(type, callExpr) ?: return
|
||||
val classDescriptor = type.classDescriptor() ?: return
|
||||
val jetTypeFqn = type.getJetTypeFqName(false)
|
||||
|
||||
val builtIns = callExpr.builtIns
|
||||
val factory = when {
|
||||
jetTypeFqn == "java.util.concurrent.ConcurrentHashMap" -> MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE
|
||||
|
||||
classDescriptor.isSubclassOf(builtIns.list) && jetTypeFqn != "kotlin.collections.List" -> {
|
||||
if (classDescriptor.isSubclassOf(builtIns.mutableList)) {
|
||||
if (jetTypeFqn != "kotlin.collections.MutableList" && jetTypeFqn != "java.util.List") {
|
||||
MiraiConsoleErrors.USING_DERIVED_MUTABLE_LIST_TYPE
|
||||
} else null
|
||||
} else MiraiConsoleErrors.USING_DERIVED_LIST_TYPE
|
||||
}
|
||||
|
||||
classDescriptor.isSubclassOf(builtIns.map) && jetTypeFqn != "kotlin.collections.Map" -> {
|
||||
if (classDescriptor.isSubclassOf(builtIns.mutableMap)) {
|
||||
if (jetTypeFqn != "kotlin.collections.MutableMap" && jetTypeFqn != "java.util.Map") {
|
||||
MiraiConsoleErrors.USING_DERIVED_MUTABLE_MAP_TYPE
|
||||
} else null
|
||||
} else MiraiConsoleErrors.USING_DERIVED_MAP_TYPE
|
||||
}
|
||||
|
||||
else -> return
|
||||
} ?: return
|
||||
|
||||
context.report(factory.on(inspectionTarget, callExpr, jetTypeFqn.substringAfterLast('.')))
|
||||
}
|
||||
|
||||
private fun checkConstructableAndSerializable(type: KotlinType, callExpr: KtCallExpression, context: DeclarationCheckerContext) {
|
||||
val classDescriptor = type.constructor.declarationDescriptor?.castOrNull<ClassDescriptor>() ?: return
|
||||
val classDescriptor = type.classDescriptor() ?: return
|
||||
|
||||
if (canBeSerializedInternally(classDescriptor)) return
|
||||
|
||||
val inspectionTarget = kotlin.run {
|
||||
val fqName = type.fqName ?: return@run null
|
||||
callExpr.typeArguments.find { it.typeReference?.isReferencing(fqName) == true }
|
||||
} ?: return
|
||||
val inspectionTarget = retrieveInspectionTarget(type, callExpr) ?: return
|
||||
|
||||
if (!classDescriptor.hasNoArgConstructor())
|
||||
return context.report(
|
||||
@ -121,10 +148,18 @@ class PluginDataValuesChecker : DeclarationChecker {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun KotlinType.classDescriptor() = constructor.declarationDescriptor?.castOrNull<ClassDescriptor>()
|
||||
|
||||
private fun retrieveInspectionTarget(type: KotlinType, callExpr: KtCallExpression): KtTypeProjection? {
|
||||
val fqName = type.fqName ?: return null
|
||||
return callExpr.typeArguments.find { it.typeReference?.isReferencing(fqName) == true }
|
||||
}
|
||||
}
|
||||
|
||||
private fun canBeSerializedInternally(descriptor: ClassDescriptor): Boolean {
|
||||
@Suppress("UNUSED_VARIABLE") val name = when (descriptor.defaultType.getJetTypeFqName(false)) {
|
||||
// kotlinx.serialization
|
||||
"kotlin.Unit" -> "UnitSerializer"
|
||||
"Z", "kotlin.Boolean" -> "BooleanSerializer"
|
||||
"B", "kotlin.Byte" -> "ByteSerializer"
|
||||
@ -168,6 +203,11 @@ private fun canBeSerializedInternally(descriptor: ClassDescriptor): Boolean {
|
||||
"java.util.Map", "java.util.LinkedHashMap" -> "LinkedHashMapSerializer"
|
||||
"java.util.HashMap" -> "HashMapSerializer"
|
||||
"java.util.Map.Entry" -> "MapEntrySerializer"
|
||||
|
||||
// mirai
|
||||
"java.util.concurrent.ConcurrentMap",
|
||||
"java.util.concurrent.ConcurrentHashMap",
|
||||
-> "ConcurrentMap" // dummy name
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
|
108
tools/intellij-plugin/src/diagnostics/fix/TypeProjectionFix.kt
Normal file
108
tools/intellij-plugin/src/diagnostics/fix/TypeProjectionFix.kt
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtTypeProjection
|
||||
|
||||
abstract class AbstractTypeProjectionFix(
|
||||
element: KtTypeProjection,
|
||||
private val newTypeFqn: String,
|
||||
) : KotlinCrossLanguageQuickFixAction<KtTypeProjection>(element), KotlinUniversalQuickFix {
|
||||
override fun getFamilyName(): String = "Mirai console"
|
||||
override fun getText(): String = "转化为 ${newTypeFqn.substringAfterLast('.')}"
|
||||
|
||||
override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) {
|
||||
val element = element ?: return
|
||||
project.executeWriteCommand(name) {
|
||||
val arguments = element.text.substringAfter('<', "")
|
||||
|
||||
val e = element.replaced(
|
||||
KtPsiFactory(project).createTypeArgument(
|
||||
if (arguments.isBlank()) {
|
||||
newTypeFqn
|
||||
} else "$newTypeFqn<$arguments"
|
||||
)
|
||||
)
|
||||
ShortenReferences.DEFAULT.process(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertToMutableMapFix(
|
||||
element: KtTypeProjection,
|
||||
) : AbstractTypeProjectionFix(element, "kotlin.collections.MutableMap") {
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
return ConvertToMutableMapFix(diagnostic.psiElement as? KtTypeProjection ?: return null)
|
||||
}
|
||||
|
||||
override fun isApplicableForCodeFragment(): Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertToMapFix(
|
||||
element: KtTypeProjection,
|
||||
) : AbstractTypeProjectionFix(element, "kotlin.collections.Map") {
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
return ConvertToMapFix(diagnostic.psiElement as? KtTypeProjection ?: return null)
|
||||
}
|
||||
|
||||
override fun isApplicableForCodeFragment(): Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertToConcurrentMapFix(
|
||||
element: KtTypeProjection,
|
||||
) : AbstractTypeProjectionFix(element, "java.util.concurrent.ConcurrentHashMap") {
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
return ConvertToConcurrentMapFix(diagnostic.psiElement as? KtTypeProjection ?: return null)
|
||||
}
|
||||
|
||||
override fun isApplicableForCodeFragment(): Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertToListFix(
|
||||
element: KtTypeProjection,
|
||||
) : AbstractTypeProjectionFix(element, "kotlin.collections.List") {
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
return ConvertToListFix(diagnostic.psiElement as? KtTypeProjection ?: return null)
|
||||
}
|
||||
|
||||
override fun isApplicableForCodeFragment(): Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertToMutableListFix(
|
||||
element: KtTypeProjection,
|
||||
) : AbstractTypeProjectionFix(element, "kotlin.collections.MutableList") {
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
return ConvertToListFix(diagnostic.psiElement as? KtTypeProjection ?: return null)
|
||||
}
|
||||
|
||||
override fun isApplicableForCodeFragment(): Boolean = false
|
||||
}
|
||||
}
|
@ -167,6 +167,11 @@ fun KtDeclaration.bodyCalls(bindingContext: BindingContext): Sequence<ResolvedCa
|
||||
val call = declaration.getResolvedCall(bindingContext) ?: return null
|
||||
sequenceOf(ResolvedCallWithExpr(call, declaration))
|
||||
}
|
||||
is KtProperty -> {
|
||||
val expr = declaration.delegateExpression ?: return null
|
||||
val call = expr.getResolvedCall(bindingContext) ?: return null
|
||||
sequenceOf(ResolvedCallWithExpr(call, expr))
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user