From 062227e072b9bac9e86dfd756e04c641a24d9d38 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Sun, 29 Nov 2020 10:41:36 +0800
Subject: [PATCH] IJ plugin: More flexible internal structure for element
 checkers

---
 .../ContextualParametersChecker.kt            | 54 +++++++++++++------
 1 file changed, 37 insertions(+), 17 deletions(-)

diff --git a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt
index 86a906bf0..4defb39d8 100644
--- a/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt
+++ b/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt
@@ -9,7 +9,6 @@
 
 package net.mamoe.mirai.console.intellij.diagnostics
 
-import com.intellij.psi.PsiElement
 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_NAME
@@ -26,9 +25,12 @@ import net.mamoe.mirai.console.intellij.util.RequirementParser
 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
 import org.jetbrains.kotlin.diagnostics.Diagnostic
 import org.jetbrains.kotlin.psi.KtDeclaration
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.ValueArgument
 import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
 import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
 import java.util.*
+import kotlin.reflect.KFunction2
 
 /**
  * Checks parameters with [ResolveContextKind]
@@ -46,7 +48,7 @@ class ContextualParametersChecker : DeclarationChecker {
         private val SEMANTIC_VERSIONING_REGEX =
             Regex("""^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?${'$'}""")
 
-        fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkPluginId(inspectionTarget: KtElement, value: String): Diagnostic? {
             if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax")
             if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(
                 inspectionTarget,
@@ -65,7 +67,7 @@ class ContextualParametersChecker : DeclarationChecker {
             return null
         }
 
-        fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkPluginName(inspectionTarget: KtElement, value: String): Diagnostic? {
             if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空")
             val lowercaseName = value.toLowerCase()
             FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
@@ -74,14 +76,14 @@ class ContextualParametersChecker : DeclarationChecker {
             return null
         }
 
-        fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkPluginVersion(inspectionTarget: KtElement, value: String): Diagnostic? {
             if (!SEMANTIC_VERSIONING_REGEX.matches(value)) {
                 return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/")
             }
             return null
         }
 
-        fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkCommandName(inspectionTarget: KtElement, value: String): Diagnostic? {
             return when {
                 value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空")
                 value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格")
@@ -91,7 +93,7 @@ class ContextualParametersChecker : DeclarationChecker {
             }
         }
 
-        fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkPermissionNamespace(inspectionTarget: KtElement, value: String): Diagnostic? {
             return when {
                 value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空")
                 value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "不允许权限命名空间中存在空格")
@@ -100,7 +102,7 @@ class ContextualParametersChecker : DeclarationChecker {
             }
         }
 
-        fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkPermissionName(inspectionTarget: KtElement, value: String): Diagnostic? {
             return when {
                 value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空")
                 value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "不允许权限名称中存在空格")
@@ -109,7 +111,7 @@ class ContextualParametersChecker : DeclarationChecker {
             }
         }
 
-        fun checkPermissionId(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkPermissionId(inspectionTarget: KtElement, value: String): Diagnostic? {
             return when {
                 value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空")
                 value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格")
@@ -119,7 +121,7 @@ class ContextualParametersChecker : DeclarationChecker {
         }
 
         @Suppress("UNUSED_PARAMETER")
-        fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? {
+        fun checkVersionRequirement(inspectionTarget: KtElement, value: String): Diagnostic? {
             return try {
                 RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value))
                 null
@@ -129,8 +131,26 @@ class ContextualParametersChecker : DeclarationChecker {
         }
     }
 
-    private val checkersMap: EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?> =
-        EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?>(ResolveContextKind::class.java).apply {
+    fun interface ElementChecker {
+        operator fun invoke(declaration: KtElement, valueArgument: ValueArgument, value: String?): Diagnostic?
+    }
+
+    private val stringCheckersMap: EnumMap<ResolveContextKind, ElementChecker> =
+        EnumMap<ResolveContextKind, ElementChecker>(ResolveContextKind::class.java).apply {
+
+            fun put(key: ResolveContextKind, value: KFunction2<KtElement, String, Diagnostic?>): ElementChecker? {
+                return put(key) { d, _, v ->
+                    if (v != null) value(d, v)
+                    else null
+                }
+            }
+
+            fun put(key: ResolveContextKind, value: KFunction2<KtElement, ValueArgument, Diagnostic?>): ElementChecker? {
+                return put(key) { d, v, _ ->
+                    value(d, v)
+                }
+            }
+
             put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName)
             put(ResolveContextKind.PLUGIN_ID, ::checkPluginId)
             put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion)
@@ -153,20 +173,20 @@ class ContextualParametersChecker : DeclarationChecker {
             }
             .mapNotNull { (p, a) ->
                 p.resolveContextKinds
-                    ?.map(checkersMap::get)
+                    ?.map(stringCheckersMap::get)
                     ?.mapNotNull {
                         if (it == null) null else it to a
                     }
             }
             .flatMap { it.asSequence() }
             .mapNotNull { (kind, argument) ->
-                argument.resolveStringConstantValues()?.let { const ->
-                    Triple(kind, argument, const)
-                }
+                Triple(kind, argument, argument.resolveStringConstantValues())
             }
             .forEach { (fn, argument, resolvedConstants) ->
-                for (resolvedConstant in resolvedConstants) {
-                    fn(argument.asElement(), resolvedConstant)?.let { context.report(it) }
+                if (resolvedConstants == null) {
+                    fn(argument.asElement(), argument, null)?.let { context.report(it) }
+                } else for (resolvedConstant in resolvedConstants) {
+                    fn(argument.asElement(), argument, resolvedConstant)?.let { context.report(it) }
                 }
             }
         return