mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
IDEA Plugin: Requirement checking
This commit is contained in:
parent
635d0bfdec
commit
2132e4d095
@ -51,6 +51,9 @@ object MiraiConsoleErrors {
|
||||
@JvmField
|
||||
val ILLEGAL_PERMISSION_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR)
|
||||
|
||||
@JvmField
|
||||
val ILLEGAL_VERSION_REQUIREMENT = create<PsiElement, String, String>(ERROR)
|
||||
|
||||
@Suppress("ObjectPropertyName", "unused")
|
||||
@JvmField
|
||||
@Deprecated("", level = DeprecationLevel.ERROR)
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_REGISTER_USE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
@ -87,6 +88,13 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension {
|
||||
Renderers.DECLARATION_NAME,
|
||||
Renderers.STRING
|
||||
)
|
||||
|
||||
put(
|
||||
ILLEGAL_VERSION_REQUIREMENT,
|
||||
"{1}",
|
||||
Renderers.STRING,
|
||||
Renderers.STRING
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMap() = MAP
|
||||
|
@ -8,6 +8,7 @@ import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
const val T = "org.example" // 编译期常量
|
||||
|
||||
@ -24,6 +25,18 @@ object MyPluginMain : KotlinPlugin(
|
||||
PermissionService.INSTANCE.register(permissionId("dvs"), "ok")
|
||||
PermissionService.INSTANCE.register(permissionId("perm with space"), "error")
|
||||
PermissionId("Namespace with space", "Name with space")
|
||||
SemVersion.parseRangeRequirement("")
|
||||
SemVersion.parseRangeRequirement("<br/>")
|
||||
SemVersion.parseRangeRequirement("SB YELLOW")
|
||||
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 || ")
|
||||
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0")
|
||||
SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 && 3.0.0")
|
||||
SemVersion.parseRangeRequirement("{}")
|
||||
SemVersion.parseRangeRequirement("||")
|
||||
SemVersion.parseRangeRequirement(">= 114.514 || = 1919.810 || (1.1, 1.2)")
|
||||
SemVersion.parseRangeRequirement("0.0.0 || {90.48}")
|
||||
SemVersion.parseRangeRequirement("{114514.1919810}")
|
||||
SemVersion.parseRangeRequirement("}")
|
||||
}
|
||||
|
||||
fun test() {
|
||||
|
@ -15,11 +15,14 @@ import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.IL
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues
|
||||
import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments
|
||||
import net.mamoe.mirai.console.intellij.util.RequirementHelper
|
||||
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
|
||||
@ -45,8 +48,10 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
|
||||
fun checkPluginId(inspectionTarget: PsiElement, 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,
|
||||
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax")
|
||||
if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on(
|
||||
inspectionTarget,
|
||||
"插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax"
|
||||
)
|
||||
|
||||
val lowercaseId = value.toLowerCase()
|
||||
|
||||
@ -115,9 +120,12 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? {
|
||||
// TODO: 2020/10/23 checkVersionRequirement
|
||||
// 实现: 先在 MiraiConsoleErrors 添加一个 error, 再检测 value 并 report 一个错误.
|
||||
return null
|
||||
return try {
|
||||
RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value))
|
||||
null
|
||||
} catch (err: Throwable) {
|
||||
ILLEGAL_VERSION_REQUIREMENT.on(inspectionTarget, value, err.message ?: err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
43
tools/intellij-plugin/src/util/RequirementHelper.kt
Normal file
43
tools/intellij-plugin/src/util/RequirementHelper.kt
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.util
|
||||
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
object RequirementHelper {
|
||||
private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex()
|
||||
private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex()
|
||||
private val versionMathRange =
|
||||
"""([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex()
|
||||
private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex()
|
||||
|
||||
private val SEM_VERSION_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-]+)*))?$""".toRegex()
|
||||
|
||||
fun isValid(rule: String): Boolean {
|
||||
return rule.trim().let {
|
||||
directVersion.matches(it) ||
|
||||
versionSelect.matches(it) ||
|
||||
versionMathRange.matches(it) ||
|
||||
versionRule.matches(it)
|
||||
}
|
||||
}
|
||||
|
||||
internal object RequirementChecker : RequirementParser.ProcessorBase<Unit>() {
|
||||
override fun processLogic(isAnd: Boolean, chunks: Iterable<Unit>) {
|
||||
}
|
||||
|
||||
override fun processString(reader: RequirementParser.TokenReader, token: RequirementParser.Token.Content) {
|
||||
if (!isValid(token.content)) {
|
||||
token.ia(reader, "`${token.content}` 无效.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
335
tools/intellij-plugin/src/util/RequirementParser.kt
Normal file
335
tools/intellij-plugin/src/util/RequirementParser.kt
Normal file
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.intellij.util
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal class RequirementParser {
|
||||
sealed class Token {
|
||||
open var line: Int = -1
|
||||
open var pos: Int = -1
|
||||
open var sourcePos: Int = -1
|
||||
open lateinit var content: String
|
||||
|
||||
sealed class GroupBod : Token() {
|
||||
class Left : GroupBod() {
|
||||
override var content: String
|
||||
get() = "{"
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
class Right : GroupBod() {
|
||||
override var content: String
|
||||
get() = "}"
|
||||
set(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Logic : Token() {
|
||||
class And : Logic() {
|
||||
override var content: String
|
||||
get() = "&&"
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
class Or : Logic() {
|
||||
override var content: String
|
||||
get() = "||"
|
||||
set(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
class Content : Token()
|
||||
class Ending : Token() {
|
||||
override var content: String
|
||||
get() = ""
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
object Begin : Token() {
|
||||
override var content: String
|
||||
get() = ""
|
||||
set(_) {}
|
||||
override var line: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
override var pos: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
override var sourcePos: Int
|
||||
get() = 0
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val END = '\u0000'
|
||||
}
|
||||
|
||||
class TokenReader(
|
||||
@JvmField val content: String
|
||||
) {
|
||||
@JvmField
|
||||
var pos: Int = 0
|
||||
|
||||
@JvmField
|
||||
var line: Int = 0
|
||||
|
||||
@JvmField
|
||||
var posi: Int = 0
|
||||
|
||||
@JvmField
|
||||
var latestToken: Token = Token.Begin
|
||||
|
||||
@JvmField
|
||||
var insertToken: Token? = Token.Begin
|
||||
fun peekChar(): Char {
|
||||
if (pos < content.length)
|
||||
return content[pos]
|
||||
return END
|
||||
}
|
||||
|
||||
fun peekNextChar(): Char {
|
||||
if (pos + 1 < content.length)
|
||||
return content[pos + 1]
|
||||
return END
|
||||
}
|
||||
|
||||
fun nextChar(): Char {
|
||||
val char = peekChar()
|
||||
pos++
|
||||
if (char == '\n') {
|
||||
line++
|
||||
posi = 0
|
||||
} else {
|
||||
posi++
|
||||
}
|
||||
return char
|
||||
}
|
||||
|
||||
fun nextToken(): Token {
|
||||
insertToken?.let { insertToken = null; return it }
|
||||
return nextToken0().also { latestToken = it }
|
||||
}
|
||||
|
||||
private fun nextToken0(): Token {
|
||||
if (pos < content.length) {
|
||||
while (peekChar().isWhitespace()) {
|
||||
nextChar()
|
||||
}
|
||||
val startIndex = pos
|
||||
if (startIndex >= content.length) {
|
||||
return Token.Ending().also {
|
||||
it.line = line
|
||||
it.pos = posi
|
||||
it.sourcePos = content.length
|
||||
}
|
||||
}
|
||||
val pline = line
|
||||
val ppos = posi
|
||||
nextChar()
|
||||
when (content[startIndex]) {
|
||||
'&' -> {
|
||||
if (peekChar() == '&') {
|
||||
return Token.Logic.And().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
nextChar()
|
||||
}
|
||||
}
|
||||
}
|
||||
'|' -> {
|
||||
if (peekChar() == '|') {
|
||||
return Token.Logic.Or().also {
|
||||
nextChar()
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' -> {
|
||||
return Token.GroupBod.Left().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
'}' -> {
|
||||
return Token.GroupBod.Right().also {
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
when (val c = peekChar()) {
|
||||
'&', '|' -> {
|
||||
if (c == peekNextChar()) {
|
||||
break
|
||||
}
|
||||
nextChar()
|
||||
}
|
||||
'{', '}' -> {
|
||||
break
|
||||
}
|
||||
END -> break
|
||||
else -> nextChar()
|
||||
}
|
||||
}
|
||||
val endIndex = pos
|
||||
return Token.Content().also {
|
||||
it.content = content.substring(startIndex, endIndex)
|
||||
it.pos = ppos
|
||||
it.line = pline
|
||||
it.sourcePos = startIndex
|
||||
}
|
||||
}
|
||||
return Token.Ending().also {
|
||||
it.line = line
|
||||
it.pos = posi
|
||||
it.sourcePos = content.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface TokensProcessor<R> {
|
||||
fun process(reader: TokenReader): R
|
||||
fun processLine(reader: TokenReader): R
|
||||
fun processLogic(isAnd: Boolean, chunks: Iterable<R>): R
|
||||
}
|
||||
|
||||
abstract class ProcessorBase<R> : TokensProcessor<R> {
|
||||
fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing {
|
||||
throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause)
|
||||
}
|
||||
|
||||
fun cutSource(reader: TokenReader, index: Int): String {
|
||||
val content = reader.content
|
||||
val s = max(0, index - 10)
|
||||
val e = min(content.length, index + 10)
|
||||
return content.substring(s, e)
|
||||
}
|
||||
|
||||
override fun process(reader: TokenReader): R {
|
||||
return when (val nextToken = reader.nextToken()) {
|
||||
is Token.Begin,
|
||||
is Token.GroupBod.Left -> {
|
||||
val first = when (val next = reader.nextToken()) {
|
||||
is Token.Content -> {
|
||||
processString(reader, next)
|
||||
}
|
||||
is Token.GroupBod.Right -> {
|
||||
nextToken.ia(
|
||||
reader, if (nextToken is Token.Begin)
|
||||
"无效的关键字 `}`"
|
||||
else "空规则组"
|
||||
)
|
||||
}
|
||||
is Token.Logic -> {
|
||||
nextToken.ia(reader, "规则不允许以逻辑操作符开始")
|
||||
}
|
||||
is Token.Ending -> {
|
||||
nextToken.ia(
|
||||
reader, if (nextToken is Token.Begin)
|
||||
"规则为空"
|
||||
else "需要更多内容"
|
||||
)
|
||||
}
|
||||
is Token.GroupBod.Left -> {
|
||||
reader.insertToken = next
|
||||
process(reader)
|
||||
}
|
||||
else -> {
|
||||
next.ia(reader, "Bad token $next")
|
||||
}
|
||||
}
|
||||
// null -> not set
|
||||
// true -> AND mode
|
||||
// false-> OR mode
|
||||
var mode: Boolean? = null
|
||||
val chunks = arrayListOf(first)
|
||||
while (true) {
|
||||
when (val next = reader.nextToken()) {
|
||||
is Token.Ending,
|
||||
is Token.GroupBod.Right -> {
|
||||
val isEndingOfGroup = next is Token.GroupBod.Right
|
||||
val isStartingOfGroup = nextToken is Token.GroupBod.Left
|
||||
if (isStartingOfGroup != isEndingOfGroup) {
|
||||
fun getType(type: Boolean) = if (type) "`}`" else "<结束>"
|
||||
next.ia(reader, "需要 ${getType(isStartingOfGroup)}, 但是找到了 ${getType(isEndingOfGroup)}")
|
||||
} else {
|
||||
// reader.insertToken = next
|
||||
break
|
||||
}
|
||||
}
|
||||
is Token.Logic -> {
|
||||
val stx = next is Token.Logic.And
|
||||
if (mode == null) mode = stx
|
||||
else if (mode != stx) {
|
||||
fun getMode(type: Boolean) = if (type) "`&&`" else "`||`"
|
||||
next.ia(
|
||||
reader, "为了避免语义混乱, 不允许在一层规则组混合使用 `&&` 和 `||`, 请显式使用 `{}` 分离. " +
|
||||
"需要 ${getMode(mode)}, 但是找到了 ${getMode(stx)}"
|
||||
)
|
||||
}
|
||||
chunks.add(process(reader))
|
||||
}
|
||||
else -> {
|
||||
next.ia(
|
||||
reader, "Except ${
|
||||
when (mode) {
|
||||
null -> "`&&` or `||`"
|
||||
true -> "`&&`"
|
||||
false -> "`||`"
|
||||
}
|
||||
} but get `${next.content}`"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == null) {
|
||||
first
|
||||
} else {
|
||||
processLogic(mode, chunks)
|
||||
}
|
||||
}
|
||||
is Token.Content -> {
|
||||
processString(reader, nextToken)
|
||||
}
|
||||
is Token.Ending -> {
|
||||
nextToken.ia(reader, "需要更多值.")
|
||||
}
|
||||
else -> {
|
||||
nextToken.ia(reader, "Assert Error: $nextToken")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun processString(reader: TokenReader, token: Token.Content): R
|
||||
|
||||
|
||||
override fun processLine(reader: TokenReader): R {
|
||||
return process(reader).also {
|
||||
val tok = reader.nextToken()
|
||||
if (reader.nextToken() !is Token.Ending) {
|
||||
tok.ia(reader, "Token Reader 未完成解析")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user