Improve performance of IDEA plugin

This commit is contained in:
Him188 2021-12-16 14:17:50 +00:00
parent aa915085b8
commit 92465c3213
12 changed files with 126 additions and 154 deletions

View File

@ -54,9 +54,9 @@ object Versions {
// If you the versions below, you need to sync changes to mirai-console/buildSrc/src/main/kotlin/Versions.kt // If you the versions below, you need to sync changes to mirai-console/buildSrc/src/main/kotlin/Versions.kt
const val yamlkt = "0.10.2" const val yamlkt = "0.10.2"
const val intellijGradlePlugin = "1.1" const val intellijGradlePlugin = "1.3.0"
const val kotlinIntellijPlugin = "211-1.5.20-release-284-IJ7442.40" // keep to newest as kotlinCompiler // const val kotlinIntellijPlugin = "211-1.5.20-release-284-IJ7442.40" // keep to newest as kotlinCompiler
const val intellij = "2021.1.3" // don't update easily unless you want your disk space -= 500MB const val intellij = "2021.3" // don't update easily unless you want your disk space -= 500MB
} }

View File

@ -67,7 +67,7 @@ object MiraiConsoleErrors {
val ILLEGAL_COMMAND_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR) val ILLEGAL_COMMAND_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR)
@JvmField @JvmField
val RESTRICTED_CONSOLE_COMMAND_OWNER = create<KtElement>(WARNING) val RESTRICTED_CONSOLE_COMMAND_OWNER = create<PsiElement>(WARNING)
@JvmField @JvmField
val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR) val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR)

View File

@ -24,11 +24,6 @@ repositories {
version = Versions.console version = Versions.console
description = "IntelliJ plugin for Mirai Console" description = "IntelliJ plugin for Mirai Console"
// JVM fails to compile
kotlin.target.compilations.forEach { kotlinCompilation ->
kotlinCompilation.kotlinOptions.freeCompilerArgs += "-Xuse-ir"
} // don't use `useIr()`, compatibility with mirai-console dedicated builds
// See https://github.com/JetBrains/gradle-intellij-plugin/ // See https://github.com/JetBrains/gradle-intellij-plugin/
intellij { intellij {
version.set(Versions.intellij) version.set(Versions.intellij)
@ -39,9 +34,10 @@ intellij {
plugins.set( plugins.set(
listOf( listOf(
"org.jetbrains.kotlin:${Versions.kotlinIntellijPlugin}", // @eap // "org.jetbrains.kotlin:${Versions.kotlinIntellijPlugin}", // @eap
"java", "java",
"gradle" "gradle",
"org.jetbrains.kotlin"
) )
) )
} }
@ -68,8 +64,6 @@ fun File.resolveMkdir(relative: String): File {
kotlin.target.compilations.all { kotlin.target.compilations.all {
kotlinOptions { kotlinOptions {
apiVersion = "1.4"
languageVersion = "1.4"
jvmTarget = "11" jvmTarget = "11"
} }
} }
@ -83,7 +77,7 @@ tasks.withType<org.jetbrains.intellij.tasks.PatchPluginXmlTask> {
<h3>Features</h3> <h3>Features</h3>
<ul> <ul>
<li>Inspections for plugin properties, for example, checking PluginDescription.</li> <li>Inspections for plugin properties.</li>
<li>Inspections for illegal calls.</li> <li>Inspections for illegal calls.</li>
<li>Intentions for resolving serialization problems.</li> <li>Intentions for resolving serialization problems.</li>
</ul> </ul>
@ -103,13 +97,6 @@ dependencies {
api(project(":mirai-console-compiler-common")) api(project(":mirai-console-compiler-common"))
compileOnly(`kotlin-stdlib-jdk8`) implementation(`kotlin-stdlib-jdk8`)
// compileOnly("com.jetbrains:ideaIC:${Versions.intellij}") implementation(`kotlin-reflect`)
// compileOnly(`kotlin-compiler`)
// compileOnly(files("libs/ide-common.jar"))
compileOnly(fileTree("run/idea-sandbox/plugins/Kotlin/lib").filter {
!it.name.contains("stdlib") && !it.name.contains("coroutines")
})
compileOnly(`kotlin-reflect`)
} }

View File

@ -1,7 +1,7 @@
plugins { plugins {
kotlin("jvm") version "1.4.20" kotlin("jvm") version "1.6.0"
kotlin("plugin.serialization") version "1.4.20" kotlin("plugin.serialization") version "1.6.0"
id("net.mamoe.mirai-console") version "2.4-M1" id("net.mamoe.mirai-console") version "2.9.0-M1"
java java
} }

View File

@ -25,7 +25,7 @@ object MyPluginMain : KotlinPlugin(
PermissionService.INSTANCE.register(permissionId("dvs"), "ok") PermissionService.INSTANCE.register(permissionId("dvs"), "ok")
PermissionService.INSTANCE.register(permissionId("perm with space"), "error") PermissionService.INSTANCE.register(permissionId("perm with space"), "error")
PermissionId("Namespace with space", "Name with space") PermissionId("Namespace with space", "Name with space")
SemVersion.parseRangeRequirement("") SemVersion.parseRangeRequirement("1.0")
SemVersion.parseRangeRequirement("<br/>") SemVersion.parseRangeRequirement("<br/>")
SemVersion.parseRangeRequirement("SB YELLOW") SemVersion.parseRangeRequirement("SB YELLOW")
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 || ") SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 || ")

View File

@ -9,6 +9,7 @@
package org.example.myplugin package org.example.myplugin
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.data.value
import org.example.myplugin.DataTest1.provideDelegate import org.example.myplugin.DataTest1.provideDelegate
@ -16,5 +17,8 @@ import org.example.myplugin.DataTest1.provideDelegate
object DataTest2 : ReadOnlyPluginConfig("data") { object DataTest2 : ReadOnlyPluginConfig("data") {
var pp by value<String>() var pp by value<String>()
val x by value<V>(V(""))
// var should be reported // var should be reported
class V constructor(val s: String)
} }

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.console.intellij package net.mamoe.mirai.console.intellij
import com.intellij.psi.PsiElement
import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker
import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker
@ -25,6 +26,9 @@ import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.idea.core.unwrapModuleSourceInfo import org.jetbrains.kotlin.idea.core.unwrapModuleSourceInfo
import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import java.io.File import java.io.File
@ -37,7 +41,8 @@ class IDEContainerContributor : StorageComponentContainerContributor {
) { ) {
if (moduleDescriptor.hasMiraiConsoleDependency()) { if (moduleDescriptor.hasMiraiConsoleDependency()) {
container.useInstance(ContextualParametersChecker().wrapIgnoringExceptionIfNotDebug()) container.useInstance(ContextualParametersChecker().wrapIgnoringExceptionIfNotDebug())
container.useInstance(PluginDataValuesChecker().wrapIgnoringExceptionIfNotDebug()) container.useInstance((PluginDataValuesChecker() as CallChecker).wrapIgnoringExceptionIfNotDebug())
container.useInstance((PluginDataValuesChecker() as DeclarationChecker).wrapIgnoringExceptionIfNotDebug())
container.useInstance(CommandDeclarationChecker().wrapIgnoringExceptionIfNotDebug()) container.useInstance(CommandDeclarationChecker().wrapIgnoringExceptionIfNotDebug())
} }
} }
@ -49,6 +54,22 @@ class IDEContainerContributor : StorageComponentContainerContributor {
return DeclarationCheckerIgnoringExceptions(this) return DeclarationCheckerIgnoringExceptions(this)
} }
private fun CallChecker.wrapIgnoringExceptionIfNotDebug(): CallChecker {
if (DEBUG_ENABLED) {
return this
}
return CallCheckerIgnoringExceptions(this)
}
class CallCheckerIgnoringExceptions(
private val delegate: CallChecker
) : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
runIgnoringErrors { delegate.check(resolvedCall, reportOn, context) }
}
}
class DeclarationCheckerIgnoringExceptions( class DeclarationCheckerIgnoringExceptions(
private val delegate: DeclarationChecker private val delegate: DeclarationChecker
) : DeclarationChecker { ) : DeclarationChecker {

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.console.intellij.diagnostics package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.psi.PsiElement
import net.mamoe.mirai.console.compiler.common.CheckerConstants import net.mamoe.mirai.console.compiler.common.CheckerConstants
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID
@ -20,47 +21,57 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RE
import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds
import net.mamoe.mirai.console.intellij.resolve.bodyCalls import net.mamoe.mirai.console.intellij.resolve.getResolvedCall
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues
import net.mamoe.mirai.console.intellij.util.RequirementHelper import net.mamoe.mirai.console.intellij.util.RequirementHelper
import net.mamoe.mirai.console.intellij.util.RequirementParser import net.mamoe.mirai.console.intellij.util.RequirementParser
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.idea.inspections.collections.isCalling
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtReferenceExpression import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import java.util.* import java.util.*
import kotlin.reflect.KFunction2 import kotlin.reflect.KFunction2
val CallCheckerContext.bindingContext get() = trace.bindingContext
/** /**
* Checks parameters with [ResolveContextKind] * Checks parameters with [ResolveContextKind]
*/ */
class ContextualParametersChecker : DeclarationChecker { class ContextualParametersChecker : CallChecker {
override fun check( override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
declaration: KtDeclaration, for ((parameter, resolvedArgument) in resolvedCall.valueArguments) {
descriptor: DeclarationDescriptor, for (valueArgument in resolvedArgument.arguments) {
context: DeclarationCheckerContext, checkArgument(parameter, valueArgument, context, valueArgument.asElement())
) {
val calls = declaration.bodyCalls(context.bindingContext) ?: return
for ((call, _) in calls) {
for ((parameter, resolvedArgument) in call.valueArguments) {
for (valueArgument in resolvedArgument.arguments) {
checkArgument(parameter, valueArgument, context)
}
} }
} }
} }
//
// override fun check(
// declaration: KtDeclaration,
// descriptor: DeclarationDescriptor,
// context: DeclarationCheckerContext,
// ) {
// val calls = declaration.bodyCalls(context.bindingContext) ?: return
//
// for ((call, _) in calls) {
// for ((parameter, resolvedArgument) in call.valueArguments) {
// for (valueArgument in resolvedArgument.arguments) {
// checkArgument(parameter, valueArgument, context)
// }
// }
// }
// }
private fun checkArgument( private fun checkArgument(
parameter: ValueParameterDescriptor, parameter: ValueParameterDescriptor,
argument: ValueArgument, argument: ValueArgument,
context: DeclarationCheckerContext, context: CallCheckerContext,
inspectionTarget: PsiElement,
) { ) {
val elementCheckers = parameter.resolveContextKinds?.mapNotNull(checkersMap::get) ?: return val elementCheckers = parameter.resolveContextKinds?.mapNotNull(checkersMap::get) ?: return
if (elementCheckers.isEmpty()) return if (elementCheckers.isEmpty()) return
@ -69,15 +80,15 @@ class ContextualParametersChecker : DeclarationChecker {
for (elementChecker in elementCheckers) { for (elementChecker in elementCheckers) {
if (resolvedConstants.isEmpty()) { if (resolvedConstants.isEmpty()) {
elementChecker(context, argument.asElement(), argument, null)?.let { context.report(it) } elementChecker(context, inspectionTarget, argument, null)?.let { context.trace.report(it) }
} else { } else {
for (resolvedConstant in resolvedConstants) { for (resolvedConstant in resolvedConstants) {
elementChecker( elementChecker(
context, context,
argument.asElement(), inspectionTarget,
argument, argument,
resolvedConstant resolvedConstant
)?.let { context.report(it) } )?.let { context.trace.report(it) }
} }
} }
} }
@ -98,14 +109,14 @@ class ContextualParametersChecker : DeclarationChecker {
*/ */
private val SEMANTIC_VERSIONING_REGEX = Regex(SEMANTIC_VERSIONING_PATTERN) private val SEMANTIC_VERSIONING_REGEX = Regex(SEMANTIC_VERSIONING_PATTERN)
fun checkPluginId(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? {
if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax") if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax")
if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on( if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(
inspectionTarget, inspectionTarget,
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax" "插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax"
) )
val lowercaseId = value.toLowerCase() val lowercaseId = value.lowercase()
if (ID_REGEX.matchEntire(value) == null) { if (ID_REGEX.matchEntire(value) == null) {
return ILLEGAL_PLUGIN_DESCRIPTION.on( return ILLEGAL_PLUGIN_DESCRIPTION.on(
@ -120,16 +131,16 @@ class ContextualParametersChecker : DeclarationChecker {
return null return null
} }
fun checkPluginName(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? {
if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空") if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空")
val lowercaseName = value.toLowerCase() val lowercaseName = value.lowercase()
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称") return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称")
} }
return null return null
} }
fun checkPluginVersion(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? {
if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { if (!SEMANTIC_VERSIONING_REGEX.matches(value)) {
return ILLEGAL_PLUGIN_DESCRIPTION.on( return ILLEGAL_PLUGIN_DESCRIPTION.on(
inspectionTarget, inspectionTarget,
@ -139,7 +150,7 @@ class ContextualParametersChecker : DeclarationChecker {
return null return null
} }
fun checkCommandName(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? {
return when { return when {
value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空") value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空")
value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格") value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格")
@ -149,7 +160,7 @@ class ContextualParametersChecker : DeclarationChecker {
} }
} }
fun checkPermissionNamespace(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? {
return when { return when {
value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空") value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空")
value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on( value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on(
@ -162,7 +173,7 @@ class ContextualParametersChecker : DeclarationChecker {
} }
} }
fun checkPermissionName(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? {
return when { return when {
value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空") value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空")
value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "不允许权限名称中存在空格") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "不允许权限名称中存在空格")
@ -171,7 +182,7 @@ class ContextualParametersChecker : DeclarationChecker {
} }
} }
fun checkPermissionId(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkPermissionId(inspectionTarget: PsiElement, value: String): Diagnostic? {
return when { return when {
value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空") value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空")
value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格")
@ -185,7 +196,7 @@ class ContextualParametersChecker : DeclarationChecker {
} }
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun checkVersionRequirement(inspectionTarget: KtElement, value: String): Diagnostic? { fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? {
return try { return try {
RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value)) RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value))
null null
@ -195,14 +206,14 @@ class ContextualParametersChecker : DeclarationChecker {
} }
fun checkConsoleCommandOwner( fun checkConsoleCommandOwner(
context: DeclarationCheckerContext, context: CallCheckerContext,
inspectionTarget: KtElement, inspectionTarget: PsiElement,
argument: ValueArgument argument: ValueArgument
): Diagnostic? { ): Diagnostic? {
val expr = argument.getArgumentExpression() ?: return null val expr = argument.getArgumentExpression() ?: return null
if (expr is KtReferenceExpression) { if (expr is KtReferenceExpression) {
if (expr.getResolvedCall(context)?.isCalling(CONSOLE_COMMAND_OWNER_FQ_NAME) == true) { if (expr.getResolvedCall(context.bindingContext)?.isCalling(CONSOLE_COMMAND_OWNER_FQ_NAME) == true) {
return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget) return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget)
} }
} }
@ -213,8 +224,8 @@ class ContextualParametersChecker : DeclarationChecker {
fun interface ElementChecker { fun interface ElementChecker {
operator fun invoke( operator fun invoke(
context: DeclarationCheckerContext, context: CallCheckerContext,
declaration: KtElement, declaration: PsiElement,
valueArgument: ValueArgument, valueArgument: ValueArgument,
value: String? value: String?
): Diagnostic? ): Diagnostic?
@ -224,7 +235,7 @@ class ContextualParametersChecker : DeclarationChecker {
private val checkersMap: EnumMap<ResolveContextKind, ElementChecker> = private val checkersMap: EnumMap<ResolveContextKind, ElementChecker> =
EnumMap<ResolveContextKind, ElementChecker>(ResolveContextKind::class.java).apply { EnumMap<ResolveContextKind, ElementChecker>(ResolveContextKind::class.java).apply {
fun put(key: ResolveContextKind, value: KFunction2<KtElement, String, Diagnostic?>): ElementChecker? { fun put(key: ResolveContextKind, value: KFunction2<PsiElement, String, Diagnostic?>): ElementChecker? {
return put(key) { _, d, _, v -> return put(key) { _, d, _, v ->
if (v != null) value(d, v) if (v != null) value(d, v)
else null else null
@ -233,7 +244,7 @@ class ContextualParametersChecker : DeclarationChecker {
fun put( fun put(
key: ResolveContextKind, key: ResolveContextKind,
value: KFunction2<KtElement, ValueArgument, Diagnostic?> value: KFunction2<PsiElement, ValueArgument, Diagnostic?>
): ElementChecker? { ): ElementChecker? {
return put(key) { _, d, v, _ -> return put(key) { _, d, v, _ ->
value(d, v) value(d, v)
@ -242,7 +253,7 @@ class ContextualParametersChecker : DeclarationChecker {
fun put( fun put(
key: ResolveContextKind, key: ResolveContextKind,
value: (DeclarationCheckerContext, KtElement, ValueArgument) -> Diagnostic? value: (CallCheckerContext, PsiElement, ValueArgument) -> Diagnostic?
): ElementChecker? { ): ElementChecker? {
return put(key) { c, d, v, _ -> return put(key) { c, d, v, _ ->
value(c, d, v) value(c, d, v)

View File

@ -11,11 +11,11 @@
package net.mamoe.mirai.console.intellij.diagnostics package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.psi.PsiElement
import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME
import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors
import net.mamoe.mirai.console.compiler.common.resolve.* import net.mamoe.mirai.console.compiler.common.resolve.*
import net.mamoe.mirai.console.intellij.resolve.bodyCalls
import net.mamoe.mirai.console.intellij.resolve.hasSuperType import net.mamoe.mirai.console.intellij.resolve.hasSuperType
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
@ -26,6 +26,8 @@ import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
@ -33,34 +35,30 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType import org.jetbrains.kotlin.types.SimpleType
class PluginDataValuesChecker : DeclarationChecker { class PluginDataValuesChecker : CallChecker, DeclarationChecker {
/**
* [KtObjectDeclaration], [KtParameter], [KtPrimaryConstructor], [KtClass], [KtNamedFunction], [KtProperty] override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
*/ check(
resolvedCall as ResolvedCall<out CallableDescriptor>,
resolvedCall.call.callElement as? KtExpression ?: return,
context
)
}
override fun check( override fun check(
declaration: KtDeclaration, declaration: KtDeclaration,
descriptor: DeclarationDescriptor, descriptor: DeclarationDescriptor,
context: DeclarationCheckerContext context: DeclarationCheckerContext
) { ) {
val bindingContext = context.bindingContext
//println(declaration::class.qualifiedName + "\t:" + declaration.text.take(10))
if (declaration is KtProperty) { if (declaration is KtProperty) {
checkReadOnly(declaration, context) checkReadOnly(declaration, context)
} }
val calls = declaration.bodyCalls(bindingContext) ?: return
for ((call, expr) in calls) {
check(call, expr, context)
}
} }
/** /**
* Check `PluginData.value` calls * Check `PluginData.value` calls
*/ */
fun check(call: ResolvedCall<out CallableDescriptor>, expr: KtExpression, context: DeclarationCheckerContext) { fun check(call: ResolvedCall<out CallableDescriptor>, expr: KtExpression, context: CallCheckerContext) {
if (!call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME)) return if (!call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME)) return
if (expr is KtCallExpression) if (expr is KtCallExpression)
@ -87,25 +85,28 @@ class PluginDataValuesChecker : DeclarationChecker {
private fun checkConstructableAndSerializable( private fun checkConstructableAndSerializable(
call: ResolvedCall<out CallableDescriptor>, call: ResolvedCall<out CallableDescriptor>,
expr: KtCallExpression, expr: KtCallExpression,
context: DeclarationCheckerContext context: CallCheckerContext
) { ) {
if (call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) != true) return if (call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) != true) return
for ((typeParameterDescriptor, kotlinType) in call.typeArguments.entries) { for ((entry, argument) in call.typeArguments.entries.zip(expr.typeArguments)) {
if ((typeParameterDescriptor.isReified || typeParameterDescriptor.resolveContextKinds?.contains( val (parameter, kotlinType) = entry
ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR if ((parameter.isReified
) == true) || parameter.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true)
&& kotlinType is SimpleType && kotlinType is SimpleType
) { ) {
checkConstructableAndSerializable(kotlinType, expr, argument, context)
checkConstructableAndSerializable(kotlinType, expr, context) checkFixType(kotlinType, expr, argument, context)
checkFixType(kotlinType, expr, context)
} }
} }
} }
private fun checkFixType(type: KotlinType, callExpr: KtCallExpression, context: DeclarationCheckerContext) { private fun checkFixType(
val inspectionTarget = retrieveInspectionTarget(type, callExpr) ?: return type: KotlinType,
callExpr: KtCallExpression,
inspectionTarget: KtTypeProjection,
context: CallCheckerContext
) {
val classDescriptor = type.classDescriptor() ?: return val classDescriptor = type.classDescriptor() ?: return
val jetTypeFqn = type.getJetTypeFqName(false) val jetTypeFqn = type.getJetTypeFqName(false)
@ -132,22 +133,21 @@ class PluginDataValuesChecker : DeclarationChecker {
else -> return else -> return
} ?: return } ?: return
context.report(factory.on(inspectionTarget, callExpr, jetTypeFqn.substringAfterLast('.'))) context.trace.report(factory.on(inspectionTarget, callExpr, jetTypeFqn.substringAfterLast('.')))
} }
private fun checkConstructableAndSerializable( private fun checkConstructableAndSerializable(
type: KotlinType, type: KotlinType,
callExpr: KtCallExpression, callExpr: KtCallExpression,
context: DeclarationCheckerContext inspectionTarget: KtTypeProjection,
context: CallCheckerContext
) { ) {
val classDescriptor = type.classDescriptor() ?: return val classDescriptor = type.classDescriptor() ?: return
if (canBeSerializedInternally(classDescriptor)) return if (canBeSerializedInternally(classDescriptor)) return
val inspectionTarget = retrieveInspectionTarget(type, callExpr) ?: return
if (!classDescriptor.hasNoArgConstructor()) if (!classDescriptor.hasNoArgConstructor())
return context.report( return context.trace.report(
MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on( MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on(
inspectionTarget, inspectionTarget,
callExpr, callExpr,
@ -156,7 +156,7 @@ class PluginDataValuesChecker : DeclarationChecker {
) )
if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME)) if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME))
return context.report( return context.trace.report(
MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on( MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on(
inspectionTarget, inspectionTarget,
classDescriptor classDescriptor

View File

@ -20,7 +20,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix
import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction
import org.jetbrains.kotlin.idea.search.getKotlinFqName import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.ImportInsertHelper import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName

View File

@ -17,7 +17,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.idea.search.getKotlinFqName import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.type import org.jetbrains.kotlin.nj2k.postProcessing.type
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression

View File

@ -160,57 +160,6 @@ val KtAnnotationEntry.annotationClass: KtClass?
fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean =
this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName }
fun KtElement.resolveAllCalls(bindingContext: BindingContext): Sequence<ResolvedCall<*>> {
return allChildrenWithSelfSequence
.filterIsInstance<KtElement>()
.mapNotNull { it.getResolvedCall(bindingContext) }
}
data class ResolvedCallWithExpr<C : CallableDescriptor, E : KtExpression>(
val call: ResolvedCall<C>,
val expr: E
)
/**
* 只解决一层
*/
fun KtDeclaration.bodyCalls(bindingContext: BindingContext): Sequence<ResolvedCallWithExpr<out CallableDescriptor, KtExpression>>? {
return when (val declaration = this) {
is KtClassOrObject -> {
declaration.superTypeListEntries.asSequence().flatMap {
it.resolveAllCallsWithElement(bindingContext, true)
}
}
is KtDeclarationWithBody -> {
declaration.bodyExpression?.resolveAllCallsWithElement(bindingContext, false) ?: return null
}
is KtCallExpression -> {
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
}
}
fun KtElement.resolveAllCallsWithElement(
bindingContext: BindingContext,
recursive: Boolean = true
): Sequence<ResolvedCallWithExpr<out CallableDescriptor, KtExpression>> {
return (if (recursive) allChildrenWithSelfSequence else childrenWithSelf.asSequence())
.filterIsInstance<KtExpression>()
.mapNotNull { expr ->
val callee = expr.getCalleeExpressionIfAny() ?: return@mapNotNull null
val resolved = callee.getResolvedCall(bindingContext) ?: return@mapNotNull null
ResolvedCallWithExpr(resolved, expr)
}
}
fun ValueArgument.resolveStringConstantValues(bindingContext: BindingContext): Sequence<String>? { fun ValueArgument.resolveStringConstantValues(bindingContext: BindingContext): Sequence<String>? {
return this.getArgumentExpression()?.resolveStringConstantValues(bindingContext) return this.getArgumentExpression()?.resolveStringConstantValues(bindingContext)
} }
@ -234,7 +183,7 @@ fun KtReferenceExpression.typeFqName() = type()?.fqName
fun KtExpression.typeFqName() = referenceExpression()?.typeFqName() fun KtExpression.typeFqName() = referenceExpression()?.typeFqName()
fun KtElement.getResolvedCall( fun KtElement.getResolvedCall(
context: BindingContext = analyze(BodyResolveMode.PARTIAL), context: BindingContext,
): ResolvedCall<out CallableDescriptor>? { ): ResolvedCall<out CallableDescriptor>? {
return this.getCall(context)?.getResolvedCall(context) return this.getCall(context)?.getResolvedCall(context)
} }