Support checking plugin name and plugin id (ILLEGAL_PLUGIN_DESCRIPTION)

This commit is contained in:
Him188 2020-09-18 01:49:08 +08:00
parent 3fc2d7f5c1
commit 5dbf596582
9 changed files with 57 additions and 36 deletions

View File

@ -17,11 +17,13 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
* 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断. * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断.
*/ */
@ConsoleExperimentalApi @ConsoleExperimentalApi
@Target(AnnotationTarget.VALUE_PARAMETER, @Target(
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY,
AnnotationTarget.FIELD, AnnotationTarget.FIELD,
AnnotationTarget.EXPRESSION) //AnnotationTarget.EXPRESSION
@Retention(AnnotationRetention.SOURCE) )
@Retention(AnnotationRetention.BINARY)
public annotation class ResolveContext( public annotation class ResolveContext(
val kind: Kind, val kind: Kind,
) { ) {

View File

@ -10,6 +10,8 @@
package net.mamoe.mirai.console.plugin.description package net.mamoe.mirai.console.plugin.description
import com.vdurmont.semver4j.Semver import com.vdurmont.semver4j.Semver
import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.*
import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.Plugin
@ -46,6 +48,7 @@ public interface PluginDescription {
* @see ID_REGEX * @see ID_REGEX
* @see FORBIDDEN_ID_NAMES * @see FORBIDDEN_ID_NAMES
*/ */
@ResolveContext(PLUGIN_ID)
public val id: String public val id: String
/** /**
@ -60,6 +63,7 @@ public interface PluginDescription {
* *
* @see FORBIDDEN_ID_NAMES * @see FORBIDDEN_ID_NAMES
*/ */
@ResolveContext(PLUGIN_NAME)
public val name: String public val name: String
/** /**
@ -88,6 +92,7 @@ public interface PluginDescription {
* *
* @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本. * @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本.
*/ */
@ResolveContext(PLUGIN_VERSION)
public val version: Semver public val version: Semver
/** /**

View File

@ -9,7 +9,7 @@
object Versions { object Versions {
const val core = "1.3.0" const val core = "1.3.0"
const val console = "1.0-RC-dev-1" const val console = "1.0-RC-dev-2"
const val consoleGraphical = "0.0.7" const val consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0" const val consoleTerminal = "0.1.0"
const val consolePure = console const val consolePure = console

View File

@ -16,8 +16,7 @@ import org.jetbrains.kotlin.diagnostics.Errors;
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
public interface MiraiConsoleErrors { public interface MiraiConsoleErrors {
DiagnosticFactory1<PsiElement, String> ILLEGAL_PLUGIN_ID = DiagnosticFactory1.create(ERROR); DiagnosticFactory1<PsiElement, String> ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> ILLEGAL_PLUGIN_NAME = DiagnosticFactory1.create(ERROR);
@Deprecated @Deprecated
Object _init = new Object() { Object _init = new Object() {

View File

@ -9,8 +9,7 @@
package net.mamoe.mirai.console.compiler.common.diagnostics package net.mamoe.mirai.console.compiler.common.diagnostics
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_ID import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_NAME
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.Renderers import org.jetbrains.kotlin.diagnostics.rendering.Renderers
@ -18,13 +17,8 @@ import org.jetbrains.kotlin.diagnostics.rendering.Renderers
object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply { private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply {
put( put(
ILLEGAL_PLUGIN_ID, ILLEGAL_PLUGIN_DESCRIPTION,
"Illegal plugin id: '{0}'", "{0}",
Renderers.STRING
)
put(
ILLEGAL_PLUGIN_NAME,
"Illegal plugin name: '{0}'",
Renderers.STRING Renderers.STRING
) )
} }

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.console.compiler.common.resolve package net.mamoe.mirai.console.compiler.common.resolve
import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.castOrNull
import net.mamoe.mirai.console.compiler.common.firstValue
import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.constants.EnumValue import org.jetbrains.kotlin.resolve.constants.EnumValue
@ -55,6 +56,6 @@ fun Annotated.isResolveContext(kind: ResolveContextKind) = this.resolveContextKi
val Annotated.resolveContextKind: ResolveContextKind? val Annotated.resolveContextKind: ResolveContextKind?
get() { get() {
val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null
val (_, enumEntryName) = ann.allValueArguments.castOrNull<EnumValue>()?.value ?: return null // undetermined kind val (_, enumEntryName) = ann.allValueArguments.firstValue().castOrNull<EnumValue>()?.value ?: return null // undetermined kind
return ResolveContextKind.valueOf(enumEntryName.asString()) return ResolveContextKind.valueOf(enumEntryName.asString())
} }

View File

@ -22,7 +22,7 @@ dependencies {
compileOnly(kotlin("stdlib-jdk8")) compileOnly(kotlin("stdlib-jdk8"))
val core = "1.3.0" val core = "1.3.0"
val console = "1.0-RC-dev-1" val console = "1.0-RC-dev-2"
compileOnly("net.mamoe:mirai-console:$console") compileOnly("net.mamoe:mirai-console:$console")
compileOnly("net.mamoe:mirai-core:$core") compileOnly("net.mamoe:mirai-core:$core")

View File

@ -5,15 +5,14 @@ import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
object MyPluginMain : KotlinPlugin( object MyPluginMain : KotlinPlugin(
JvmPluginDescription( JvmPluginDescription(
"org.example.example-plugin", "net.mamoe.main",
"0.1.0" "0.1.0",
) ) {
name(".")
id("")
}
) { ) {
fun test() { fun test() {
} }
} }
class PM : KotlinPlugin(
)

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.console.intellij.diagnostics package net.mamoe.mirai.console.intellij.diagnostics
import com.intellij.psi.PsiElement 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.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.findChildren
@ -32,15 +33,36 @@ import kotlin.contracts.contract
*/ */
class PluginDescriptionChecker : DeclarationChecker { class PluginDescriptionChecker : DeclarationChecker {
companion object { companion object {
fun checkPluginName(declaration: KtDeclaration, value: String): Diagnostic? { private val ID_REGEX: Regex = Regex("""([a-zA-Z]+(?:\.[a-zA-Z0-9]+)*)\.([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)""")
return null // TODO: 2020/9/18 checkPluginName private val FORBIDDEN_ID_NAMES: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? {
if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin id cannot be blank")
if (value.none { it == '.' }) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget,
"'$value' is illegal. Plugin id must consist of both domain and name. ")
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}'.")
}
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal ->
return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin id contains illegal word: '$illegal'.")
}
return null
} }
fun checkPluginId(declaration: KtDeclaration, value: String): Diagnostic? { fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? {
return null // TODO: 2020/9/18 checkPluginId if (value.isBlank()) return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "Plugin name cannot be blank")
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 null
} }
fun checkPluginVersion(declaration: KtDeclaration, value: String): Diagnostic? { fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? {
return null // TODO: 2020/9/18 checkPluginVersion return null // TODO: 2020/9/18 checkPluginVersion
} }
} }
@ -56,15 +78,14 @@ class PluginDescriptionChecker : DeclarationChecker {
} }
} }
private val checkersMap: EnumMap<ResolveContextKind, (declaration: KtDeclaration, value: String) -> Diagnostic?> = private val checkersMap: EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?> =
EnumMap<ResolveContextKind, (declaration: KtDeclaration, value: String) -> Diagnostic?>(ResolveContextKind::class.java).apply { EnumMap<ResolveContextKind, (declaration: PsiElement, value: String) -> Diagnostic?>(ResolveContextKind::class.java).apply {
put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName)
put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId)
put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion) put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion)
} }
fun check( fun check(
declaration: KtDeclaration,
expression: KtCallExpression, expression: KtCallExpression,
context: DeclarationCheckerContext, context: DeclarationCheckerContext,
) { ) {
@ -76,7 +97,7 @@ class PluginDescriptionChecker : DeclarationChecker {
val value = argument.getArgumentExpression() val value = argument.getArgumentExpression()
?.resolveStringConstantValue(context.bindingContext) ?: continue ?.resolveStringConstantValue(context.bindingContext) ?: continue
for ((kind, fn) in checkersMap) { for ((kind, fn) in checkersMap) {
if (parameterContextKind == kind) fn(declaration, value)?.let { context.report(it) } if (parameterContextKind == kind) fn(argument.asElement(), value)?.let { context.report(it) }
} }
} }
} }
@ -93,11 +114,11 @@ class PluginDescriptionChecker : DeclarationChecker {
is KtObjectDeclaration -> { is KtObjectDeclaration -> {
// check super type constructor // check super type constructor
val superTypeCallEntry = declaration.findChildren<KtSuperTypeList>()?.findChildren<KtSuperTypeCallEntry>() ?: return val superTypeCallEntry = declaration.findChildren<KtSuperTypeList>()?.findChildren<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.findChildren<KtValueArgumentList>() ?: return
valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach { valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach {
if (it.shouldPerformCheck()) { if (it.shouldPerformCheck()) {
check(declaration, it as KtCallExpression, context) check(it as KtCallExpression, context)
} }
} }