mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Improve description checker: support compile-time constants
This commit is contained in:
parent
961bbfba53
commit
6a2ef97b41
BIN
tools/intellij-plugin/.images/ILLEGAL_PLUGIN_DESCRIPTION.png
Normal file
BIN
tools/intellij-plugin/.images/ILLEGAL_PLUGIN_DESCRIPTION.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -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)
|
||||||
|
@ -3,9 +3,25 @@ package org.example.myplugin
|
|||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||||
|
|
||||||
|
val T = "scas" + "pp" // 编译期常量
|
||||||
|
|
||||||
object MyPluginMain : KotlinPlugin(
|
object MyPluginMain : KotlinPlugin(
|
||||||
JvmPluginDescription(
|
JvmPluginDescription(
|
||||||
"net.mamoe.main",
|
T,
|
||||||
|
"0.1.0",
|
||||||
|
) {
|
||||||
|
name(".")
|
||||||
|
id("")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
fun test() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object MyPluginMain2 : KotlinPlugin(
|
||||||
|
JvmPluginDescription(
|
||||||
|
"",
|
||||||
"0.1.0",
|
"0.1.0",
|
||||||
) {
|
) {
|
||||||
name(".")
|
name(".")
|
||||||
|
@ -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.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.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.resolveStringConstantValue
|
||||||
import net.mamoe.mirai.console.intellij.resolve.valueParameters
|
import net.mamoe.mirai.console.intellij.resolve.valueParameters
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
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 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 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? {
|
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,
|
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()
|
val lowercaseId = value.toLowerCase()
|
||||||
|
|
||||||
if (ID_REGEX.matchEntire(value) == null) {
|
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 ->
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
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()
|
val lowercaseName = value.toLowerCase()
|
||||||
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
|
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
|
return null
|
||||||
}
|
}
|
||||||
@ -90,8 +92,7 @@ class PluginDescriptionChecker : DeclarationChecker {
|
|||||||
context: DeclarationCheckerContext,
|
context: DeclarationCheckerContext,
|
||||||
) {
|
) {
|
||||||
val call = expression.calleeExpression.getResolvedCallOrResolveToCall(context) ?: return // unresolved
|
val call = expression.calleeExpression.getResolvedCallOrResolveToCall(context) ?: return // unresolved
|
||||||
call.valueArgumentsByIndex?.forEach { resolvedValueArgument ->
|
for ((parameter, argument) in call.valueParameters.zip(call.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty())) {
|
||||||
for ((parameter, argument) in call.valueParameters.zip(resolvedValueArgument.arguments)) {
|
|
||||||
val parameterContextKind = parameter.resolveContextKind
|
val parameterContextKind = parameter.resolveContextKind
|
||||||
if (checkersMap.containsKey(parameterContextKind)) {
|
if (checkersMap.containsKey(parameterContextKind)) {
|
||||||
val value = argument.getArgumentExpression()
|
val value = argument.getArgumentExpression()
|
||||||
@ -102,7 +103,6 @@ class PluginDescriptionChecker : DeclarationChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun check(
|
override fun check(
|
||||||
declaration: KtDeclaration,
|
declaration: KtDeclaration,
|
||||||
@ -113,9 +113,9 @@ class PluginDescriptionChecker : DeclarationChecker {
|
|||||||
when (declaration) {
|
when (declaration) {
|
||||||
is KtObjectDeclaration -> {
|
is KtObjectDeclaration -> {
|
||||||
// check super type constructor
|
// 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 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 {
|
valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach {
|
||||||
if (it.shouldPerformCheck()) {
|
if (it.shouldPerformCheck()) {
|
||||||
check(it as KtCallExpression, context)
|
check(it as KtCallExpression, context)
|
||||||
@ -126,10 +126,10 @@ class PluginDescriptionChecker : DeclarationChecker {
|
|||||||
is KtClassOrObject -> {
|
is KtClassOrObject -> {
|
||||||
// check constructor
|
// 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 constructorCall = superTypeCallEntry.findChild<KtConstructorCalleeExpression>()?.resolveToCall() ?: return
|
||||||
val valueArgumentList = superTypeCallEntry.findChildren<KtValueArgumentList>() ?: return
|
val valueArgumentList = superTypeCallEntry.findChild<KtValueArgumentList>() ?: return
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.intellij.resolve
|
package net.mamoe.mirai.console.intellij.resolve
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiDeclarationStatement
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import net.mamoe.mirai.console.compiler.common.castOrNull
|
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.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
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.name.FqName
|
||||||
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
|
||||||
import org.jetbrains.kotlin.psi.*
|
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.calls.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
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(
|
fun KtElement?.getResolvedCallOrResolveToCall(
|
||||||
context: BindingContext,
|
context: BindingContext,
|
||||||
@ -111,10 +115,23 @@ val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDes
|
|||||||
|
|
||||||
fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): String? {
|
fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): String? {
|
||||||
when (this) {
|
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 -> {
|
is KtStringTemplateExpression -> {
|
||||||
if (hasInterpolation()) return null
|
if (hasInterpolation()) return null
|
||||||
return entries.joinToString("") { it.text }
|
return entries.joinToString("") { it.text }
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
is KtCallExpression -> {
|
is KtCallExpression -> {
|
||||||
val callee = this.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext)?.resultingDescriptor
|
val callee = this.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext)?.resultingDescriptor
|
||||||
if (callee is VariableDescriptor) {
|
if (callee is VariableDescriptor) {
|
||||||
@ -122,7 +139,7 @@ fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): Str
|
|||||||
return compileTimeConstant.castOrNull<StringValue>()?.value
|
return compileTimeConstant.castOrNull<StringValue>()?.value
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}*/
|
||||||
is KtConstantExpression -> {
|
is KtConstantExpression -> {
|
||||||
// TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression
|
// TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user