Improve description checker: support compile-time constants

This commit is contained in:
Him188 2020-09-18 10:07:50 +08:00
parent 961bbfba53
commit 6a2ef97b41
5 changed files with 68 additions and 25 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -5,3 +5,13 @@ IntelliJ 平台的 Mirai Console 开发插件
## 功能
### 诊断
#### ILLEGAL_PLUGIN_DESCRIPTION
[PluginDescriptionChecker.kt](src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt#L34)
- 使用 [ResolveContext](../../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt)
- 检测 Plugin Id, Plugin Name, Plugin Version 的合法性. 并在非法时提示正确的语法.
- 支持编译期常量
![ILLEGAL_PLUGIN_DESCRIPTION](.images/ILLEGAL_PLUGIN_DESCRIPTION.png)

View File

@ -3,9 +3,25 @@ package org.example.myplugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
val T = "scas" + "pp" // 编译期常量
object MyPluginMain : KotlinPlugin(
JvmPluginDescription(
"net.mamoe.main",
T,
"0.1.0",
) {
name(".")
id("")
}
) {
fun test() {
}
}
object MyPluginMain2 : KotlinPlugin(
JvmPluginDescription(
"",
"0.1.0",
) {
name(".")

View File

@ -13,7 +13,7 @@ import com.intellij.psi.PsiElement
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind
import net.mamoe.mirai.console.intellij.resolve.findChildren
import net.mamoe.mirai.console.intellij.resolve.findChild
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValue
import net.mamoe.mirai.console.intellij.resolve.valueParameters
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@ -36,28 +36,30 @@ class PluginDescriptionChecker : DeclarationChecker {
private val ID_REGEX: Regex = Regex("""([a-zA-Z]+(?:\.[a-zA-Z0-9]+)*)\.([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)""")
private val FORBIDDEN_ID_NAMES: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名. """
fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? {
if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin id cannot be blank")
if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax")
if (value.none { it == '.' }) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget,
"'$value' is illegal. Plugin id must consist of both domain and name. ")
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax")
val lowercaseId = value.toLowerCase()
if (ID_REGEX.matchEntire(value) == null) {
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin does not match regex '${ID_REGEX.pattern}'.")
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 无效. 正确的插件 Id 应该满足正则表达式 '${ID_REGEX.pattern}', \n$syntax")
}
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal ->
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin id contains illegal word: '$illegal'.")
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件 Id. 确保插件 Id 不完全是这个名称.")
}
return null
}
fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? {
if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin name cannot be blank")
if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空.")
val lowercaseName = value.toLowerCase()
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin name is illegal: '$illegal'.")
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称.")
}
return null
}
@ -90,15 +92,13 @@ class PluginDescriptionChecker : DeclarationChecker {
context: DeclarationCheckerContext,
) {
val call = expression.calleeExpression.getResolvedCallOrResolveToCall(context) ?: return // unresolved
call.valueArgumentsByIndex?.forEach { resolvedValueArgument ->
for ((parameter, argument) in call.valueParameters.zip(resolvedValueArgument.arguments)) {
val parameterContextKind = parameter.resolveContextKind
if (checkersMap.containsKey(parameterContextKind)) {
val value = argument.getArgumentExpression()
?.resolveStringConstantValue(context.bindingContext) ?: continue
for ((kind, fn) in checkersMap) {
if (parameterContextKind == kind) fn(argument.asElement(), value)?.let { context.report(it) }
}
for ((parameter, argument) in call.valueParameters.zip(call.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty())) {
val parameterContextKind = parameter.resolveContextKind
if (checkersMap.containsKey(parameterContextKind)) {
val value = argument.getArgumentExpression()
?.resolveStringConstantValue(context.bindingContext) ?: continue
for ((kind, fn) in checkersMap) {
if (parameterContextKind == kind) fn(argument.asElement(), value)?.let { context.report(it) }
}
}
}
@ -113,9 +113,9 @@ class PluginDescriptionChecker : DeclarationChecker {
when (declaration) {
is KtObjectDeclaration -> {
// check super type constructor
val superTypeCallEntry = declaration.findChildren<KtSuperTypeList>()?.findChildren<KtSuperTypeCallEntry>() ?: return
val superTypeCallEntry = declaration.findChild<KtSuperTypeList>()?.findChild<KtSuperTypeCallEntry>() ?: return
// val constructorCall = superTypeCallEntry.findChildren<KtConstructorCalleeExpression>()?.resolveToCall() ?: return
val valueArgumentList = superTypeCallEntry.findChildren<KtValueArgumentList>() ?: return
val valueArgumentList = superTypeCallEntry.findChild<KtValueArgumentList>() ?: return
valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach {
if (it.shouldPerformCheck()) {
check(it as KtCallExpression, context)
@ -126,10 +126,10 @@ class PluginDescriptionChecker : DeclarationChecker {
is KtClassOrObject -> {
// check constructor
val superTypeCallEntry = declaration.findChildren<KtSuperTypeList>()?.findChildren<KtSuperTypeCallEntry>() ?: return
val superTypeCallEntry = declaration.findChild<KtSuperTypeList>()?.findChild<KtSuperTypeCallEntry>() ?: return
val constructorCall = superTypeCallEntry.findChildren<KtConstructorCalleeExpression>()?.resolveToCall() ?: return
val valueArgumentList = superTypeCallEntry.findChildren<KtValueArgumentList>() ?: return
val constructorCall = superTypeCallEntry.findChild<KtConstructorCalleeExpression>()?.resolveToCall() ?: return
val valueArgumentList = superTypeCallEntry.findChild<KtValueArgumentList>() ?: return
}

View File

@ -9,6 +9,7 @@
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
@ -19,6 +20,8 @@ import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
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.*
@ -28,6 +31,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
/**
@ -98,7 +102,7 @@ val PsiElement.allChildrenFlat: Sequence<PsiElement>
}
}
inline fun <reified E : PsiElement> PsiElement.findChildren(): E? = this.children.find { it is E } as E?
inline fun <reified E> PsiElement.findChild(): E? = this.children.find { it is E } as E?
fun KtElement?.getResolvedCallOrResolveToCall(
context: BindingContext,
@ -111,10 +115,23 @@ val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDes
fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): String? {
when (this) {
is KtNameReferenceExpression -> {
when (val reference = references.firstIsInstance<KtSimpleNameReference>().resolve()) {
is KtDeclaration -> {
val descriptor = reference.descriptor.castOrNull<VariableDescriptor>() ?: return null
val compileTimeConstant = descriptor.compileTimeInitializer ?: return null
return compileTimeConstant.castOrNull<StringValue>()?.value
}
is PsiDeclarationStatement -> {
}
}
}
is KtStringTemplateExpression -> {
if (hasInterpolation()) return null
return entries.joinToString("") { it.text }
}
/*
is KtCallExpression -> {
val callee = this.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext)?.resultingDescriptor
if (callee is VariableDescriptor) {
@ -122,7 +139,7 @@ fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): Str
return compileTimeConstant.castOrNull<StringValue>()?.value
}
return null
}
}*/
is KtConstantExpression -> {
// TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression
}