mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
Introduce inspection NOT_CONSTRUCTABLE_TYPE for PluginData.value
This commit is contained in:
parent
e4f37b9a52
commit
7244cb76c4
@ -12,16 +12,17 @@
|
||||
package net.mamoe.mirai.console.compiler.common
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@Target(
|
||||
AnnotationTarget.VALUE_PARAMETER,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.FIELD,
|
||||
//AnnotationTarget.EXPRESSION
|
||||
VALUE_PARAMETER,
|
||||
PROPERTY, FIELD,
|
||||
FUNCTION,
|
||||
TYPE, TYPE_PARAMETER
|
||||
)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class ResolveContext(
|
||||
@ -34,5 +35,10 @@ public annotation class ResolveContext(
|
||||
PLUGIN_ID,
|
||||
PLUGIN_NAME,
|
||||
PLUGIN_VERSION,
|
||||
|
||||
/**
|
||||
* Custom serializers allowed
|
||||
*/
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
}
|
||||
}
|
@ -18,9 +18,13 @@
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
import net.mamoe.mirai.console.data.java.JAutoSavePluginData
|
||||
import net.mamoe.mirai.console.internal.data.*
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
@ -98,7 +102,7 @@ import kotlin.reflect.full.findAnnotation
|
||||
*
|
||||
* 要查看详细的解释,请查看 [docs/PluginData.md](https://github.com/mamoe/mirai-console/blob/master/docs/PluginData.md)
|
||||
*
|
||||
* @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
|
||||
* @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
|
||||
* @see PluginDataStorage [PluginData] 存储仓库
|
||||
* @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数
|
||||
*/
|
||||
@ -321,6 +325,7 @@ public inline fun <reified T> PluginData.value(
|
||||
* 通过具体化类型创建一个 [SerializerAwareValue].
|
||||
* @see valueFromKType 查看更多实现信息
|
||||
*/
|
||||
@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR)
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline fun <reified T> PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> =
|
||||
valueImpl<T>(typeOf0<T>(), T::class).also { it.value.apply() }
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
object Versions {
|
||||
const val core = "1.3.0"
|
||||
const val console = "1.0-RC-dev-2"
|
||||
const val console = "1.0-RC-dev-3"
|
||||
const val consoleGraphical = "0.0.7"
|
||||
const val consoleTerminal = "0.1.0"
|
||||
const val consolePure = console
|
||||
|
@ -17,6 +17,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
|
||||
|
||||
public interface MiraiConsoleErrors {
|
||||
DiagnosticFactory1<PsiElement, String> ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
@Deprecated
|
||||
Object _init = new Object() {
|
||||
|
@ -10,6 +10,7 @@
|
||||
package net.mamoe.mirai.console.compiler.common.diagnostics
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
|
||||
@ -21,6 +22,12 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
|
||||
"{0}",
|
||||
Renderers.STRING
|
||||
)
|
||||
|
||||
put(
|
||||
NOT_CONSTRUCTABLE_TYPE,
|
||||
"类型 {0} 无法通过反射直接构造, 需要提供默认值.",
|
||||
Renderers.STRING
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMap() = MAP
|
||||
|
@ -30,6 +30,12 @@ val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin")
|
||||
val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
|
||||
val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription")
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PluginData
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
val PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME = FqName("net.mamoe.mirai.console.data.value")
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Resolve
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -44,6 +50,7 @@ enum class ResolveContextKind {
|
||||
PLUGIN_NAME,
|
||||
PLUGIN_VERSION,
|
||||
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
@ -9,8 +9,41 @@
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common.resolve
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
|
||||
|
||||
fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqName)
|
||||
fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName)
|
||||
fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName)
|
||||
|
||||
|
||||
val PsiElement.allChildrenWithSelf: Sequence<PsiElement>
|
||||
get() = sequence {
|
||||
yield(this@allChildrenWithSelf)
|
||||
for (child in children) {
|
||||
yieldAll(child.allChildrenWithSelf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
|
||||
|
||||
|
||||
val PsiElement.parents: Sequence<PsiElement>
|
||||
get() {
|
||||
val seed = if (this is PsiFile) null else parent
|
||||
return generateSequence(seed) { if (it is PsiFile) null else it.parent }
|
||||
}
|
||||
|
||||
|
||||
fun ClassDescriptor.findNoArgConstructor(): ClassConstructorDescriptor? {
|
||||
return constructors.find { desc ->
|
||||
desc.valueParameters.all { it.hasDefaultValue() }
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassDescriptor.hasNoArgConstructor(): Boolean = this.findNoArgConstructor() != null
|
@ -22,7 +22,7 @@ dependencies {
|
||||
compileOnly(kotlin("stdlib-jdk8"))
|
||||
|
||||
val core = "1.3.0"
|
||||
val console = "1.0-RC-dev-2"
|
||||
val console = "1.0-RC-dev-3"
|
||||
|
||||
compileOnly("net.mamoe:mirai-console:$console")
|
||||
compileOnly("net.mamoe:mirai-core:$core")
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.example.myplugin
|
||||
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||
|
||||
@ -19,16 +21,15 @@ object MyPluginMain : KotlinPlugin(
|
||||
}
|
||||
}
|
||||
|
||||
object MyPluginMain2 : KotlinPlugin(
|
||||
JvmPluginDescription(
|
||||
"",
|
||||
"0.1.0",
|
||||
) {
|
||||
name(".")
|
||||
id("")
|
||||
}
|
||||
) {
|
||||
fun test() {
|
||||
object DataTest : AutoSavePluginConfig() {
|
||||
val p by value<HasDefaultValue>()
|
||||
val pp by value<NoDefaultValue>()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
data class HasDefaultValue(
|
||||
val x: Int = 0,
|
||||
)
|
||||
|
||||
data class NoDefaultValue(
|
||||
val y: Int,
|
||||
)
|
@ -9,6 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.intellij
|
||||
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker
|
||||
import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker
|
||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||
import org.jetbrains.kotlin.container.useInstance
|
||||
@ -22,5 +23,6 @@ class IDEContainerContributor : StorageComponentContainerContributor {
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
) {
|
||||
container.useInstance(PluginDescriptionChecker())
|
||||
container.useInstance(PluginDataValuesChecker())
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2020 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 net.mamoe.mirai.console.intellij.diagnostics
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.hasNoArgConstructor
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement
|
||||
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.refactoring.fqName.fqName
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
|
||||
|
||||
class PluginDataValuesChecker : DeclarationChecker {
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
val bindingContext = context.bindingContext
|
||||
declaration.resolveAllCallsWithElement(bindingContext)
|
||||
.filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) }
|
||||
.filter { (call) ->
|
||||
call.resultingDescriptor.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
}.flatMap { (call, element) ->
|
||||
call.typeArguments.entries.associateWith { element }.asSequence()
|
||||
}.filter { (e, _) ->
|
||||
val (p, t) = e
|
||||
(p.isReified || p.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR)
|
||||
&& t is SimpleType
|
||||
}.forEach { (e, callExpr) ->
|
||||
val (_, t) = e
|
||||
val classDescriptor = t.constructor.declarationDescriptor?.castOrNull<ClassDescriptor>()
|
||||
|
||||
fun getInspectionTarget(): PsiElement {
|
||||
return callExpr.typeArguments.find { it.references.firstOrNull()?.canonicalText == t.fqName?.toString() } ?: callExpr
|
||||
}
|
||||
|
||||
fun reportInspection() {
|
||||
context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
|
||||
getInspectionTarget(),
|
||||
t.fqName?.asString().toString())
|
||||
)
|
||||
}
|
||||
|
||||
when {
|
||||
classDescriptor == null -> reportInspection()
|
||||
!classDescriptor.hasNoArgConstructor() -> reportInspection()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,10 +17,10 @@ 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.resolve.getElementForLineMark
|
||||
import net.mamoe.mirai.console.intellij.resolve.parents
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtConstructor
|
||||
|
@ -11,10 +11,11 @@ package net.mamoe.mirai.console.intellij.resolve
|
||||
|
||||
import com.intellij.psi.PsiDeclarationStatement
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
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
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.allChildrenWithSelf
|
||||
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
|
||||
@ -67,16 +68,8 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? {
|
||||
return null
|
||||
}
|
||||
|
||||
inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull()
|
||||
|
||||
val KtClassOrObject.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { it.getKotlinFqName() }
|
||||
|
||||
val PsiElement.parents: Sequence<PsiElement>
|
||||
get() {
|
||||
val seed = if (this is PsiFile) null else parent
|
||||
return generateSequence(seed) { if (it is PsiFile) null else it.parent }
|
||||
}
|
||||
|
||||
fun getElementForLineMark(callElement: PsiElement): PsiElement =
|
||||
when (callElement) {
|
||||
is KtSimpleNameExpression -> callElement.getReferencedNameElement()
|
||||
@ -92,20 +85,23 @@ val KtAnnotationEntry.annotationClass: KtClass?
|
||||
fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
|
||||
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
|
||||
|
||||
val PsiElement.allChildrenWithSelf: Sequence<PsiElement>
|
||||
get() = sequence {
|
||||
yield(this@allChildrenWithSelf)
|
||||
for (child in children) {
|
||||
yieldAll(child.allChildrenWithSelf)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
|
||||
return allChildrenWithSelf
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.mapNotNull { it.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext) }
|
||||
}
|
||||
|
||||
fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence<Pair<ResolvedCall<out CallableDescriptor>, KtCallExpression>> {
|
||||
return allChildrenWithSelf
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.mapNotNull {
|
||||
val callee = it.calleeExpression ?: return@mapNotNull null
|
||||
val resolved = callee.getResolvedCallOrResolveToCall(bindingContext) ?: return@mapNotNull null
|
||||
|
||||
resolved to it
|
||||
}
|
||||
}
|
||||
|
||||
fun ResolvedCall<*>.valueParametersWithArguments(): List<Pair<ValueParameterDescriptor, ValueArgument>> {
|
||||
return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user