From 5471f7d68738e7c8b56c14bcefd80afaae723842 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 13 Sep 2020 13:21:57 +0800 Subject: [PATCH 001/114] Update Run.md --- docs/Run.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Run.md b/docs/Run.md index d5fdd2347..81a25f4ec 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -4,6 +4,8 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 ## 使用第三方工具自动启动 +https://github.com/LXY1226/MiraiOK + ## 独立启动 ### 环境 From bf5a43b6b4be61d1686267e586ad92776dc2156f Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 13 Sep 2020 15:27:42 +0800 Subject: [PATCH 002/114] Add modules: mirai-console-intellij and mirai-console-gradle --- gradle-plugin/build.gradle.kts | 67 +++++++++++ intellij-plugin/build.gradle.kts | 107 ++++++++++++++++++ intellij-plugin/libs/ide-common.jar | Bin 0 -> 441806 bytes .../BlockingBridgeLineMarkerProvider.kt | 94 +++++++++++++++ .../intellij/IDEContainerContributor.kt | 16 +++ .../net/mamoe/mirai/console/intellij/Icons.kt | 17 +++ .../MiraiConsoleDeclarationChecker.kt | 25 ++++ .../src/main/resources/META-INF/plugin.xml | 28 +++++ .../resources/icons/commandDeclaration.svg | 19 ++++ settings.gradle.kts | 2 + 10 files changed, 375 insertions(+) create mode 100644 gradle-plugin/build.gradle.kts create mode 100644 intellij-plugin/build.gradle.kts create mode 100644 intellij-plugin/libs/ide-common.jar create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/BlockingBridgeLineMarkerProvider.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt create mode 100644 intellij-plugin/src/main/resources/META-INF/plugin.xml create mode 100644 intellij-plugin/src/main/resources/icons/commandDeclaration.svg diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts new file mode 100644 index 000000000..741181c4b --- /dev/null +++ b/gradle-plugin/build.gradle.kts @@ -0,0 +1,67 @@ +@file:Suppress("UnusedImport") + +plugins { + kotlin("jvm") + id("java") + `maven-publish` + id("com.jfrog.bintray") +} + +version = Versions.console +description = "Gradle plugin for Mirai Console" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" +} + +kotlin { + sourceSets.all { + target.compilations.all { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + //useIR = true + } + } + languageSettings.apply { + progressiveMode = true + + useExperimentalAnnotation("kotlin.Experimental") + useExperimentalAnnotation("kotlin.RequiresOptIn") + + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") + useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") + useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi") + useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") + useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") + useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") + useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi") + useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi") + } + } +} + +dependencies { + api("org.jetbrains:annotations:19.0.0") + api(kotlinx("coroutines-jdk8", Versions.coroutines)) + + testApi(kotlin("test")) + testApi(kotlin("test-junit5")) + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") +} + +tasks { + "test"(Test::class) { + useJUnitPlatform() + } +} + +// setupPublishing("mirai-console-gradle") \ No newline at end of file diff --git a/intellij-plugin/build.gradle.kts b/intellij-plugin/build.gradle.kts new file mode 100644 index 000000000..33558763a --- /dev/null +++ b/intellij-plugin/build.gradle.kts @@ -0,0 +1,107 @@ +@file:Suppress("UnusedImport") + +plugins { + kotlin("jvm") + id("java") + `maven-publish` + id("com.jfrog.bintray") + + id("org.jetbrains.intellij") version "0.4.16" + +} + +repositories { + maven("http://maven.aliyun.com/nexus/content/groups/public/") +} + +version = Versions.console +description = "IntelliJ plugin for Mirai Console" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" +} + +// See https://github.com/JetBrains/gradle-intellij-plugin/ +intellij { + version = "2020.2.1" + isDownloadSources = true + updateSinceUntilBuild = false + + setPlugins( + "org.jetbrains.kotlin:1.4.10-release-IJ2020.2.1-1@staging" + ) +} + +tasks.getByName("publishPlugin", org.jetbrains.intellij.tasks.PublishTask::class) { + val pluginKey = project.findProperty("jetbrains.hub.key")?.toString() + if (pluginKey != null) { + logger.info("Found jetbrains.hub.key") + setToken(pluginKey) + } else { + logger.info("jetbrains.hub.key not found") + } +} + +tasks.withType { + sinceBuild("193.*") + untilBuild("205.*") + changeNotes(""" + Fix cancellation on analyzing augments + """.trimIndent()) +} + +kotlin { + sourceSets.all { + target.compilations.all { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + //useIR = true + } + } + languageSettings.apply { + progressiveMode = true + + useExperimentalAnnotation("kotlin.Experimental") + useExperimentalAnnotation("kotlin.RequiresOptIn") + + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") + useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") + useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi") + useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") + useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") + useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") + useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi") + useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi") + } + } +} + +dependencies { + api("org.jetbrains:annotations:19.0.0") + api(kotlinx("coroutines-jdk8", Versions.coroutines)) + + compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") + compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") + compileOnly(files("libs/ide-common.jar")) + + testApi(kotlin("test")) + testApi(kotlin("test-junit5")) + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") +} + +tasks { + "test"(Test::class) { + useJUnitPlatform() + } +} + +// setupPublishing("mirai-console-intellij") \ No newline at end of file diff --git a/intellij-plugin/libs/ide-common.jar b/intellij-plugin/libs/ide-common.jar new file mode 100644 index 0000000000000000000000000000000000000000..724e639bd677db26135449b917536cd7bea663ef GIT binary patch literal 441806 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�Qd!Yy!mqY*6i@xl7?F)M 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 { + return null + } + + override fun collectSlowLineMarkers( + elements: MutableList, + result: MutableCollection>, + ) { + val markedLineNumbers = HashSet() + + for (element in elements) { + ProgressManager.checkCanceled() + + if (element !is PsiReferenceExpression) continue + + val containingFile = element.containingFile + if (containingFile !is PsiJavaFile || containingFile is PsiJavaCodeReferenceCodeFragment) { + continue + } + + val lineNumber = element.getLineNumber() + if (lineNumber in markedLineNumbers) continue + if (!element.hasBridgeCalls()) continue + + + markedLineNumbers += lineNumber + result += if (element is KtForExpression) { + CommandDeclarationLineMarkerInfo( + getElementForLineMark(element.loopRange!!), + // KotlinBundle.message("highlighter.message.suspending.iteration") + ) + } else { + CommandDeclarationLineMarkerInfo( + getElementForLineMark(element), + //KotlinBundle.message("highlighter.message.suspend.function.call") + ) + } + } + } + + @Suppress("DEPRECATION") + class CommandDeclarationLineMarkerInfo( + callElement: PsiElement, + ) : LineMarkerInfo( + callElement, + callElement.textRange, + Icons.CommandDeclaration, + Pass.LINE_MARKERS, + { + "Mirai Console Command" + }, + null, + GutterIconRenderer.Alignment.RIGHT + ) { + override fun createGutterRenderer(): GutterIconRenderer? { + return object : LineMarkerInfo.LineMarkerGutterIconRenderer(this) { + override fun getClickAction(): AnAction? = null + } + } + } +} + +fun PsiReferenceExpression.hasBridgeCalls(): Boolean { + val resolved = this.resolve() as? KtLightMethod ?: return false + + TODO() +} + +internal fun getElementForLineMark(callElement: PsiElement): PsiElement = + when (callElement) { + is KtSimpleNameExpression -> callElement.getReferencedNameElement() + else -> + // a fallback, + //but who knows what to reference in KtArrayAccessExpression ? + generateSequence(callElement, { it.firstChild }).last() + } \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt new file mode 100644 index 000000000..cff72d5a9 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt @@ -0,0 +1,16 @@ +package net.mamoe.mirai.console.intellij + +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor + +class IDEContainerContributor : StorageComponentContainerContributor { + override fun registerModuleComponents( + container: StorageComponentContainer, + platform: org.jetbrains.kotlin.platform.TargetPlatform, + moduleDescriptor: ModuleDescriptor, + ) { + container.useInstance(MiraiConsoleDeclarationChecker()) + } +} \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt new file mode 100644 index 000000000..b4551fe87 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij + +import com.intellij.openapi.util.IconLoader +import javax.swing.Icon + +object Icons { + val CommandDeclaration: Icon = IconLoader.getIcon("/icons/commandDeclaration.svg") +} \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt new file mode 100644 index 000000000..f2d99b213 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij + +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext + +class MiraiConsoleDeclarationChecker : DeclarationChecker { + override fun check( + declaration: KtDeclaration, + descriptor: DeclarationDescriptor, + context: DeclarationCheckerContext, + ) { + + } +} \ No newline at end of file diff --git a/intellij-plugin/src/main/resources/META-INF/plugin.xml b/intellij-plugin/src/main/resources/META-INF/plugin.xml new file mode 100644 index 000000000..54c5f4070 --- /dev/null +++ b/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,28 @@ + + net.mamoe.mirai-console-dev + + Mirai Console Dev + + + Mamoe Technologies + + + com.intellij.modules.platform + org.jetbrains.kotlin + + + + + + + + + + + + + \ No newline at end of file diff --git a/intellij-plugin/src/main/resources/icons/commandDeclaration.svg b/intellij-plugin/src/main/resources/icons/commandDeclaration.svg new file mode 100644 index 000000000..1e1c7a10e --- /dev/null +++ b/intellij-plugin/src/main/resources/icons/commandDeclaration.svg @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 29aabb3f9..e1b5cfd3a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,8 @@ fun includeProject(projectPath: String, path: String? = null) { includeProject(":mirai-console", "backend/mirai-console") includeProject(":mirai-console.codegen", "backend/codegen") includeProject(":mirai-console-pure", "frontend/mirai-console-pure") +includeProject(":mirai-console-intellij", "intellij-plugin") +includeProject(":mirai-console-gradle", "gradle-plugin") @Suppress("ConstantConditionIf") if (!disableOldFrontEnds) { From 9351128f386ba7df6e5f53cf160dae4348b180c1 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 13 Sep 2020 21:26:49 +0800 Subject: [PATCH 003/114] Support gutters for Plugin main declaration --- intellij-plugin/build.gradle.kts | 2 +- intellij-plugin/run/projects/.gitignore | 5 + .../projects/test-project/build.gradle.kts | 43 ++++ .../projects/test-project/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58694 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../run/projects/test-project/gradlew | 183 ++++++++++++++++++ .../run/projects/test-project/gradlew.bat | 103 ++++++++++ .../projects/test-project/settings.gradle.kts | 2 + .../org/example/myplugin/MyPluginMain.kt | 165 ++++++++++++++++ .../src/test/kotlin/RunConsole.kt | 17 ++ .../BlockingBridgeLineMarkerProvider.kt | 94 --------- .../net/mamoe/mirai/console/intellij/Icons.kt | 1 + .../net/mamoe/mirai/console/intellij/Mirai.kt | 14 ++ .../marker/PluginMainLineMarkerProvider.kt | 88 +++++++++ .../console/intellij/line/marker/util.kt | 52 +++++ .../src/main/resources/META-INF/plugin.xml | 4 +- .../resources/icons/pluginMainDeclaration.png | Bin 0 -> 3629 bytes 18 files changed, 683 insertions(+), 96 deletions(-) create mode 100644 intellij-plugin/run/projects/.gitignore create mode 100644 intellij-plugin/run/projects/test-project/build.gradle.kts create mode 100644 intellij-plugin/run/projects/test-project/gradle.properties create mode 100644 intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar create mode 100644 intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties create mode 100644 intellij-plugin/run/projects/test-project/gradlew create mode 100644 intellij-plugin/run/projects/test-project/gradlew.bat create mode 100644 intellij-plugin/run/projects/test-project/settings.gradle.kts create mode 100644 intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt create mode 100644 intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt delete mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/BlockingBridgeLineMarkerProvider.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt create mode 100644 intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png diff --git a/intellij-plugin/build.gradle.kts b/intellij-plugin/build.gradle.kts index 33558763a..ace7adb38 100644 --- a/intellij-plugin/build.gradle.kts +++ b/intellij-plugin/build.gradle.kts @@ -33,7 +33,7 @@ intellij { updateSinceUntilBuild = false setPlugins( - "org.jetbrains.kotlin:1.4.10-release-IJ2020.2.1-1@staging" + "org.jetbrains.kotlin:1.4.10-release-IJ2020.2-1@staging" ) } diff --git a/intellij-plugin/run/projects/.gitignore b/intellij-plugin/run/projects/.gitignore new file mode 100644 index 000000000..3d1806f15 --- /dev/null +++ b/intellij-plugin/run/projects/.gitignore @@ -0,0 +1,5 @@ +local.properties +build/ +build +.gradle +.idea diff --git a/intellij-plugin/run/projects/test-project/build.gradle.kts b/intellij-plugin/run/projects/test-project/build.gradle.kts new file mode 100644 index 000000000..e134da7b7 --- /dev/null +++ b/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + kotlin("jvm") version "1.4.0" + kotlin("plugin.serialization") version "1.4.0" + kotlin("kapt") version "1.4.0" + id("com.github.johnrengelman.shadow") version "5.2.0" +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenLocal() + jcenter() + mavenCentral() +} + +kotlin.sourceSets.all { + languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") +} + +dependencies { + compileOnly(kotlin("stdlib-jdk8")) + + val core = "1.2.3" + val console = "1.0-M4" + + compileOnly("net.mamoe:mirai-console:$console") + compileOnly("net.mamoe:mirai-core:$core") + + val autoService = "1.0-rc7" + kapt("com.google.auto.service", "auto-service", autoService) + compileOnly("com.google.auto.service", "auto-service-annotations", autoService) + + testImplementation("net.mamoe:mirai-console:$console") + testImplementation("net.mamoe:mirai-core:$core") + testImplementation("net.mamoe:mirai-console-pure:$console") + testImplementation(kotlin("stdlib-jdk8")) +} + +kotlin.target.compilations.all { + kotlinOptions.freeCompilerArgs += "-Xjvm-default=enable" + kotlinOptions.jvmTarget = "1.8" +} \ No newline at end of file diff --git a/intellij-plugin/run/projects/test-project/gradle.properties b/intellij-plugin/run/projects/test-project/gradle.properties new file mode 100644 index 000000000..7fc6f1ff2 --- /dev/null +++ b/intellij-plugin/run/projects/test-project/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar b/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..490fda8577df6c95960ba7077c43220e5bb2c0d9 GIT binary patch literal 58694 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 literal 0 HcmV?d00001 diff --git a/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties b/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..a4b442974 --- /dev/null +++ b/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/intellij-plugin/run/projects/test-project/gradlew b/intellij-plugin/run/projects/test-project/gradlew new file mode 100644 index 000000000..2fe81a7d9 --- /dev/null +++ b/intellij-plugin/run/projects/test-project/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/intellij-plugin/run/projects/test-project/gradlew.bat b/intellij-plugin/run/projects/test-project/gradlew.bat new file mode 100644 index 000000000..62bd9b9cc --- /dev/null +++ b/intellij-plugin/run/projects/test-project/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/intellij-plugin/run/projects/test-project/settings.gradle.kts b/intellij-plugin/run/projects/test-project/settings.gradle.kts new file mode 100644 index 000000000..0e9d380ad --- /dev/null +++ b/intellij-plugin/run/projects/test-project/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "test-project" + diff --git a/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt new file mode 100644 index 000000000..4a39b56e5 --- /dev/null +++ b/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -0,0 +1,165 @@ +@file:Suppress("unused") + +package org.example.myplugin + +import com.google.auto.service.AutoService +import kotlinx.serialization.Serializable +import net.mamoe.mirai.Bot +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.CompositeCommand +import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.SimpleCommand +import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.AutoSavePluginData +import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys +import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault +import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.permission.PermissionService +import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription +import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import net.mamoe.mirai.console.util.scopeWith +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.utils.info + +@AutoService(JvmPlugin::class) +object MyPluginMain : KotlinPlugin( + JvmPluginDescription( + "org.example.example-plugin", + "0.1.0" + ) +) { + + val PERMISSION_EXECUTE_1 = PermissionService.INSTANCE.register( + permissionId("execute1"), + "注册权限的示例" + ) + + + override fun onEnable() { + MySetting.reload() // 从数据库自动读取配置实例 + MyPluginData.reload() + + logger.info { "Hi: ${MySetting.name}" } // 输出一条日志. + logger.info("Hi: ${MySetting.name}") // 输出一条日志. 与上面一条相同, 但更推荐上面一条. + logger.verbose("Hi: ${MySetting.name}") // 多种日志级别可选 + + // 请不要使用 println, System.out.println 等标准输出方式. 请总是使用 logger. + + + MySetting.count++ // 对 Setting 的改动会自动在合适的时间保存 + + MySimpleCommand.register() // 注册指令 + } + + override fun onDisable() { + MySimpleCommand.unregister() // 取消注册指令 + } +} + +// 定义插件数据 +// 插件 +object MyPluginData : AutoSavePluginData() { + var list: MutableList by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略 + var long: Long by value(0L) // 允许 var + var int by value(0) // 可以使用类型推断, 但更推荐使用 `var long: Long by value(0)` 这种定义方式. + + + // 带默认值的非空 map. + // notnullMap[1] 的返回值总是非 null 的 MutableMap + var notnullMap + by value>>().withEmptyDefault() + + // 可将 MutableMap 映射到 MutableMap. + val botToLongMap: MutableMap by value>().mapKeys(Bot::getInstance, Bot::id) +} + +// 定义一个配置. 所有属性都会被追踪修改, 并自动保存. +// 配置是插件与用户交互的接口, 但不能用来保存插件的数据. +object MySetting : AutoSavePluginConfig() { + val name by value("test") + + var count by value(0) + + val nested by value() // 嵌套类型是支持的 +} + +@Serializable +data class MyNestedData( + val list: List = listOf(), +) + +// 简单指令 +object MySimpleCommand : SimpleCommand( + MyPluginMain, "foo", + description = "示例指令" +) { + // 会自动创建一个 ID 为 "org.example.example-plugin:command.foo" 的权限. + + + // 通过 /foo 调用, 参数自动解析 + @Handler + suspend fun CommandSender.handle(int: Int, str: String) { // 函数名随意, 但参数需要按顺序放置. + + if (this.hasPermission(MyPluginMain.PERMISSION_EXECUTE_1)) { + sendMessage("你有 ${MyPluginMain.PERMISSION_EXECUTE_1.id} 权限.") + } else { + sendMessage( + """ + 你没有 ${MyPluginMain.PERMISSION_EXECUTE_1.id} 权限. + 可以在控制台使用 /permission 管理权限. + """.trimIndent() + ) + } + + sendMessage("/foo 的第一个参数是 $int, 第二个是 $str") + } +} + +// 复合指令 +object MyCompositeCommand : CompositeCommand( + MyPluginMain, "manage", + description = "示例指令", + // prefixOptional = true // 还有更多参数可填, 此处忽略 +) { + // 会自动创建一个 ID 为 "org.example.example-plugin:command.manage" 的权限. + + // [参数智能解析] + // + // 在控制台执行 "/manage <群号>.<群员> <持续时间>", + // 或在聊天群内发送 "/manage <@一个群员> <持续时间>", + // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>", + // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>" + // 时调用这个函数 + @SubCommand + suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute 调用 + sendMessage("/manage mute 被调用了, 参数为: $target, $duration") + + val result = kotlin.runCatching { + target.mute(duration).toString() + }.getOrElse { + it.stackTraceToString() + } // 失败时返回堆栈信息 + + + // 表示对 this 和 ConsoleCommandSender 一起操作 + this.scopeWith(ConsoleCommandSender) { + sendMessage("结果: $result") // 同时发送给 this@CommandSender 和 ConsoleCommandSender + } + } + + @SubCommand + suspend fun CommandSender.list() { // 执行 "/manage list" 时调用这个函数 + sendMessage("/manage list 被调用了") + } + + // 支持 Image 类型, 需在聊天中执行此指令. + @SubCommand + suspend fun CommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数 + sendMessage("/manage image 被调用了, 图片是 ${image.imageId}") + } +} \ No newline at end of file diff --git a/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt b/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt new file mode 100644 index 000000000..c4856993e --- /dev/null +++ b/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt @@ -0,0 +1,17 @@ +import net.mamoe.mirai.alsoLogin +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.load +import net.mamoe.mirai.console.pure.MiraiConsolePureLoader +import org.example.myplugin.MyPluginMain + +suspend fun main() { + MiraiConsolePureLoader.startAsDaemon() + + MyPluginMain.load() // 主动加载插件, Console 会调用 MyPluginMain.onLoad + MyPluginMain.enable() // 主动启用插件, Console 会调用 MyPluginMain.onEnable + + val bot = MiraiConsole.addBot(123456, "").alsoLogin() // 登录一个测试环境的 Bot + + MiraiConsole.job.join() +} \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/BlockingBridgeLineMarkerProvider.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/BlockingBridgeLineMarkerProvider.kt deleted file mode 100644 index ccaf76327..000000000 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/BlockingBridgeLineMarkerProvider.kt +++ /dev/null @@ -1,94 +0,0 @@ -package net.mamoe.mirai.console.intellij - -import com.intellij.codeHighlighting.Pass -import com.intellij.codeInsight.daemon.LineMarkerInfo -import com.intellij.codeInsight.daemon.LineMarkerProvider -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.editor.markup.GutterIconRenderer -import com.intellij.openapi.progress.ProgressManager -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiJavaCodeReferenceCodeFragment -import com.intellij.psi.PsiJavaFile -import com.intellij.psi.PsiReferenceExpression -import org.jetbrains.kotlin.asJava.elements.KtLightMethod -import org.jetbrains.kotlin.idea.core.util.getLineNumber -import org.jetbrains.kotlin.psi.KtForExpression -import org.jetbrains.kotlin.psi.KtSimpleNameExpression - -class MiraiConsoleLineMarkerProvider : LineMarkerProvider { - override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { - return null - } - - override fun collectSlowLineMarkers( - elements: MutableList, - result: MutableCollection>, - ) { - val markedLineNumbers = HashSet() - - for (element in elements) { - ProgressManager.checkCanceled() - - if (element !is PsiReferenceExpression) continue - - val containingFile = element.containingFile - if (containingFile !is PsiJavaFile || containingFile is PsiJavaCodeReferenceCodeFragment) { - continue - } - - val lineNumber = element.getLineNumber() - if (lineNumber in markedLineNumbers) continue - if (!element.hasBridgeCalls()) continue - - - markedLineNumbers += lineNumber - result += if (element is KtForExpression) { - CommandDeclarationLineMarkerInfo( - getElementForLineMark(element.loopRange!!), - // KotlinBundle.message("highlighter.message.suspending.iteration") - ) - } else { - CommandDeclarationLineMarkerInfo( - getElementForLineMark(element), - //KotlinBundle.message("highlighter.message.suspend.function.call") - ) - } - } - } - - @Suppress("DEPRECATION") - class CommandDeclarationLineMarkerInfo( - callElement: PsiElement, - ) : LineMarkerInfo( - callElement, - callElement.textRange, - Icons.CommandDeclaration, - Pass.LINE_MARKERS, - { - "Mirai Console Command" - }, - null, - GutterIconRenderer.Alignment.RIGHT - ) { - override fun createGutterRenderer(): GutterIconRenderer? { - return object : LineMarkerInfo.LineMarkerGutterIconRenderer(this) { - override fun getClickAction(): AnAction? = null - } - } - } -} - -fun PsiReferenceExpression.hasBridgeCalls(): Boolean { - val resolved = this.resolve() as? KtLightMethod ?: return false - - TODO() -} - -internal fun getElementForLineMark(callElement: PsiElement): PsiElement = - when (callElement) { - is KtSimpleNameExpression -> callElement.getReferencedNameElement() - else -> - // a fallback, - //but who knows what to reference in KtArrayAccessExpression ? - generateSequence(callElement, { it.firstChild }).last() - } \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt index b4551fe87..ae7217290 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt @@ -14,4 +14,5 @@ import javax.swing.Icon object Icons { val CommandDeclaration: Icon = IconLoader.getIcon("/icons/commandDeclaration.svg") + val PluginMainDeclaration: Icon = IconLoader.getIcon("/icons/pluginMainDeclaration.png") } \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt new file mode 100644 index 000000000..595f0f2e7 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij + +import org.jetbrains.kotlin.name.FqName + +val Plugin_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt new file mode 100644 index 000000000..45c6b1aed --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt @@ -0,0 +1,88 @@ +package net.mamoe.mirai.console.intellij.line.marker + +import com.intellij.codeHighlighting.Pass +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.codeInsight.daemon.LineMarkerProvider +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.editor.markup.GutterIconRenderer +import com.intellij.openapi.progress.ProgressManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiJavaCodeReferenceCodeFragment +import net.mamoe.mirai.console.intellij.Icons +import net.mamoe.mirai.console.intellij.Plugin_FQ_NAME +import org.jetbrains.kotlin.idea.core.util.getLineNumber +import org.jetbrains.kotlin.nj2k.postProcessing.resolve +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtConstructor +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.KtReferenceExpression + +class PluginMainLineMarkerProvider : LineMarkerProvider { + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { + return null + } + + override fun collectSlowLineMarkers( + elements: MutableList, + result: MutableCollection>, + ) { + val markedLineNumbers = HashSet() + + for (element in elements) { + ProgressManager.checkCanceled() + + + val containingFile = element.containingFile + if (containingFile is PsiJavaCodeReferenceCodeFragment) { + continue + } + + + + + when { + element is KtReferenceExpression // constructor call + -> { + val objectDeclaration = + element.parents.filterIsInstance().firstOrNull() ?: continue + + val resolved = element.resolve() as? KtConstructor<*> ?: continue + + val kotlinPluginClass = resolved.parent as? KtClass ?: continue + + if (kotlinPluginClass.allSuperNames.none { it == Plugin_FQ_NAME }) continue + + val lineNumber = objectDeclaration.getLineNumber() + if (lineNumber in markedLineNumbers) continue + + markedLineNumbers += lineNumber + result += Info(getElementForLineMark(objectDeclaration)) + } + } + + // if (!element.hasBridgeCalls()) continue + + } + } + + @Suppress("DEPRECATION") + class Info( + callElement: PsiElement, + ) : LineMarkerInfo( + callElement, + callElement.textRange, + Icons.PluginMainDeclaration, + Pass.LINE_MARKERS, + { + "Mirai Console Plugin" + }, + null, + GutterIconRenderer.Alignment.RIGHT + ) { + override fun createGutterRenderer(): GutterIconRenderer? { + return object : LineMarkerInfo.LineMarkerGutterIconRenderer(this) { + override fun getClickAction(): AnAction? = null + } + } + } +} \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt new file mode 100644 index 000000000..38c96eb3c --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ +package net.mamoe.mirai.console.intellij.line.marker + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiReferenceExpression +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.nj2k.postProcessing.resolve +import org.jetbrains.kotlin.psi.* + + +internal val KtPureClassOrObject.allSuperTypes: Sequence + get() = sequence { + yieldAll(superTypeListEntries) + for (list in superTypeListEntries.asSequence()) { + yieldAll((list.typeAsUserType?.referenceExpression?.resolve() as? KtClass)?.allSuperTypes.orEmpty()) + } + } + +internal inline fun PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() + +internal val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } + +fun PsiReferenceExpression.hasBridgeCalls(): Boolean { + val resolved = this.resolve() as? KtLightMethod ?: return false + + TODO() +} + +val PsiElement.parents: Sequence + get() { + val seed = if (this is PsiFile) null else parent + return generateSequence(seed) { if (it is PsiFile) null else it.parent } + } + +internal fun getElementForLineMark(callElement: PsiElement): PsiElement = + when (callElement) { + is KtSimpleNameExpression -> callElement.getReferencedNameElement() + else -> + // a fallback, + //but who knows what to reference in KtArrayAccessExpression ? + generateSequence(callElement, { it.firstChild }).last() + } \ No newline at end of file diff --git a/intellij-plugin/src/main/resources/META-INF/plugin.xml b/intellij-plugin/src/main/resources/META-INF/plugin.xml index 54c5f4070..969686c19 100644 --- a/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -14,7 +14,9 @@ + implementationClass="net.mamoe.mirai.console.intellij.line.marker.PluginMainLineMarkerProvider"/> + diff --git a/intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png b/intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png new file mode 100644 index 0000000000000000000000000000000000000000..7b107ff3da05a7c7130b01dd6445d5d17a1ccc30 GIT binary patch literal 3629 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 P2c6eqANPZ<> Date: Sun, 13 Sep 2020 22:52:11 +0800 Subject: [PATCH 004/114] Support line markers for CommandDeclaration --- .../org/example/myplugin/MyPluginMain.kt | 2 - .../org/example/myplugin/MySimpleCommand.kt | 14 +++++ .../CommandDeclarationLineMarkerProvider.kt | 40 ++++++++++++++ .../marker/PluginMainLineMarkerProvider.kt | 55 +++---------------- .../console/intellij/line/marker/util.kt | 8 ++- .../console/intellij/resolve/psiResolve.kt | 30 ++++++++++ .../src/main/resources/META-INF/plugin.xml | 4 ++ 7 files changed, 103 insertions(+), 50 deletions(-) create mode 100644 intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt create mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt diff --git a/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 4a39b56e5..4b0dfda16 100644 --- a/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -128,13 +128,11 @@ object MyCompositeCommand : CompositeCommand( ) { // 会自动创建一个 ID 为 "org.example.example-plugin:command.manage" 的权限. - // [参数智能解析] // // 在控制台执行 "/manage <群号>.<群员> <持续时间>", // 或在聊天群内发送 "/manage <@一个群员> <持续时间>", // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>", // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>" - // 时调用这个函数 @SubCommand suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute 调用 sendMessage("/manage mute 被调用了, 参数为: $target, $duration") diff --git a/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt new file mode 100644 index 000000000..30699c49a --- /dev/null +++ b/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt @@ -0,0 +1,14 @@ +package org.example.myplugin + +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.SimpleCommand + +object MySimpleCommand000 : SimpleCommand( + MyPluginMain, "foo", + description = "示例指令" +) { + @Handler + suspend fun CommandSender.handle(int: Int, str: String) { + + } +} diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt new file mode 100644 index 000000000..b05df3175 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt @@ -0,0 +1,40 @@ +package net.mamoe.mirai.console.intellij.line.marker + +import com.intellij.codeHighlighting.Pass +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.codeInsight.daemon.LineMarkerProvider +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.editor.markup.GutterIconRenderer +import com.intellij.psi.PsiElement +import net.mamoe.mirai.console.intellij.Icons +import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandlerOrCompositeCommandSubCommand +import org.jetbrains.kotlin.psi.KtNamedFunction + +class CommandDeclarationLineMarkerProvider : LineMarkerProvider { + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { + if (element !is KtNamedFunction) return null + if (!element.isSimpleCommandHandlerOrCompositeCommandSubCommand()) return null + return Info(getElementForLineMark(element.funKeyword ?: element.identifyingElement ?: element)) + } + + @Suppress("DEPRECATION") + class Info( + callElement: PsiElement, + ) : LineMarkerInfo( + callElement, + callElement.textRange, + Icons.CommandDeclaration, + Pass.LINE_MARKERS, + { + "子指令定义" + }, + null, + GutterIconRenderer.Alignment.RIGHT + ) { + override fun createGutterRenderer(): GutterIconRenderer? { + return object : LineMarkerInfo.LineMarkerGutterIconRenderer(this) { + override fun getClickAction(): AnAction? = null + } + } + } +} \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt index 45c6b1aed..22d8407f4 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt @@ -5,12 +5,10 @@ import com.intellij.codeInsight.daemon.LineMarkerInfo import com.intellij.codeInsight.daemon.LineMarkerProvider import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.editor.markup.GutterIconRenderer -import com.intellij.openapi.progress.ProgressManager import com.intellij.psi.PsiElement -import com.intellij.psi.PsiJavaCodeReferenceCodeFragment +import com.intellij.util.castSafelyTo import net.mamoe.mirai.console.intellij.Icons import net.mamoe.mirai.console.intellij.Plugin_FQ_NAME -import org.jetbrains.kotlin.idea.core.util.getLineNumber import org.jetbrains.kotlin.nj2k.postProcessing.resolve import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtConstructor @@ -19,50 +17,13 @@ import org.jetbrains.kotlin.psi.KtReferenceExpression class PluginMainLineMarkerProvider : LineMarkerProvider { override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { - return null - } - - override fun collectSlowLineMarkers( - elements: MutableList, - result: MutableCollection>, - ) { - val markedLineNumbers = HashSet() - - for (element in elements) { - ProgressManager.checkCanceled() - - - val containingFile = element.containingFile - if (containingFile is PsiJavaCodeReferenceCodeFragment) { - continue - } - - - - - when { - element is KtReferenceExpression // constructor call - -> { - val objectDeclaration = - element.parents.filterIsInstance().firstOrNull() ?: continue - - val resolved = element.resolve() as? KtConstructor<*> ?: continue - - val kotlinPluginClass = resolved.parent as? KtClass ?: continue - - if (kotlinPluginClass.allSuperNames.none { it == Plugin_FQ_NAME }) continue - - val lineNumber = objectDeclaration.getLineNumber() - if (lineNumber in markedLineNumbers) continue - - markedLineNumbers += lineNumber - result += Info(getElementForLineMark(objectDeclaration)) - } - } - - // if (!element.hasBridgeCalls()) continue - - } + if (element !is KtReferenceExpression) return null + val objectDeclaration = + element.parents.filterIsInstance().firstOrNull() ?: return null + val kotlinPluginClass = + element.resolve().castSafelyTo>()?.parent?.castSafelyTo() ?: return null + if (kotlinPluginClass.allSuperNames.none { it == Plugin_FQ_NAME }) return null + return Info(getElementForLineMark(objectDeclaration)) } @Suppress("DEPRECATION") diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt index 38c96eb3c..4e93675e5 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt @@ -49,4 +49,10 @@ internal fun getElementForLineMark(callElement: PsiElement): PsiElement = // a fallback, //but who knows what to reference in KtArrayAccessExpression ? generateSequence(callElement, { it.firstChild }).last() - } \ No newline at end of file + } + +internal val KtAnnotationEntry.annotationClass: KtClass? + get() = calleeExpression?.constructorReferenceExpression?.resolve()?.findParent() + +internal fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = + this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt new file mode 100644 index 000000000..0504c573f --- /dev/null +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij.resolve + +import net.mamoe.mirai.console.intellij.line.marker.hasAnnotation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtNamedFunction + +val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") +val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") + +/** + * For CompositeCommand.SubCommand + */ +fun KtNamedFunction.isCompositeCommandSubCommand(): Boolean = this.hasAnnotation(COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME) + +/** + * SimpleCommand.Handler + */ +fun KtNamedFunction.isSimpleCommandHandler(): Boolean = this.hasAnnotation(SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME) + +fun KtNamedFunction.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean = + this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand() \ No newline at end of file diff --git a/intellij-plugin/src/main/resources/META-INF/plugin.xml b/intellij-plugin/src/main/resources/META-INF/plugin.xml index 969686c19..c7d524f96 100644 --- a/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -17,6 +17,10 @@ implementationClass="net.mamoe.mirai.console.intellij.line.marker.PluginMainLineMarkerProvider"/> + + From c994f837379194511ebf5b5aae0865487a51a182 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 13 Sep 2020 22:55:03 +0800 Subject: [PATCH 005/114] Add missing copyright, cleanup --- .../console/intellij/IDEContainerContributor.kt | 9 +++++++++ .../net/mamoe/mirai/console/intellij/Mirai.kt | 14 -------------- .../marker/CommandDeclarationLineMarkerProvider.kt | 9 +++++++++ .../line/marker/PluginMainLineMarkerProvider.kt | 11 ++++++++++- .../mirai/console/intellij/line/marker/util.kt | 1 + .../mirai/console/intellij/resolve/psiResolve.kt | 2 ++ 6 files changed, 31 insertions(+), 15 deletions(-) delete mode 100644 intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt index cff72d5a9..ef7231786 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + package net.mamoe.mirai.console.intellij import org.jetbrains.kotlin.container.StorageComponentContainer diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt deleted file mode 100644 index 595f0f2e7..000000000 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Mirai.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.intellij - -import org.jetbrains.kotlin.name.FqName - -val Plugin_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt index b05df3175..144ea41f9 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + package net.mamoe.mirai.console.intellij.line.marker import com.intellij.codeHighlighting.Pass diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt index 22d8407f4..866d22378 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt @@ -1,3 +1,12 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + package net.mamoe.mirai.console.intellij.line.marker import com.intellij.codeHighlighting.Pass @@ -8,7 +17,7 @@ import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import com.intellij.util.castSafelyTo import net.mamoe.mirai.console.intellij.Icons -import net.mamoe.mirai.console.intellij.Plugin_FQ_NAME +import net.mamoe.mirai.console.intellij.resolve.Plugin_FQ_NAME import org.jetbrains.kotlin.nj2k.postProcessing.resolve import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtConstructor diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt index 4e93675e5..d75f2e05b 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt @@ -6,6 +6,7 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ + package net.mamoe.mirai.console.intellij.line.marker import com.intellij.psi.PsiElement diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt index 0504c573f..c72204ade 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt +++ b/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt @@ -16,6 +16,8 @@ import org.jetbrains.kotlin.psi.KtNamedFunction val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") +val Plugin_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") + /** * For CompositeCommand.SubCommand */ From be311923d02d1ab5f2c10cfa3449ad337f9018f2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 14 Sep 2020 12:48:38 +0800 Subject: [PATCH 006/114] Add descriptions for permissions --- .../mirai/console/command/BuiltInCommands.kt | 27 ++++++++++--- .../mirai/console/permission/PermitteeId.kt | 28 ++++++++++++++ docs/Permissions.md | 38 ++++++++++++++++++- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index 919818b8f..764573ccf 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -148,7 +148,7 @@ public object BuiltInCommands { public object PermissionCommand : CompositeCommand( ConsoleCommandOwner, "permission", "权限", "perm", - description = "Manage permissions", + description = "管理权限", overrideContext = buildCommandArgumentContext { PermitteeId::class with PermitteeIdArgumentParser Permission::class with PermissionIdArgumentParser.map { id -> @@ -159,30 +159,47 @@ public object BuiltInCommands { }, ), BuiltInCommandInternal { // TODO: 2020/9/10 improve Permission command + + @Description("授权一个权限") @SubCommand("permit", "grant", "add") - public suspend fun CommandSender.permit(target: PermitteeId, permission: Permission) { + public suspend fun CommandSender.permit( + @Name("被许可人 ID") target: PermitteeId, + @Name("权限 ID") permission: Permission, + ) { target.grantPermission(permission) sendMessage("OK") } + @Description("取消授权一个权限") @SubCommand("cancel", "deny", "remove") - public suspend fun CommandSender.cancel(target: PermitteeId, permission: Permission) { + public suspend fun CommandSender.cancel( + @Name("被许可人 ID") target: PermitteeId, + @Name("权限 ID") permission: Permission, + ) { target.denyPermission(permission, false) sendMessage("OK") } + @Description("取消授权一个权限及其所有子权限") @SubCommand("cancelAll", "denyAll", "removeAll") - public suspend fun CommandSender.cancelAll(target: PermitteeId, permission: Permission) { + public suspend fun CommandSender.cancelAll( + @Name("被许可人 ID") target: PermitteeId, + @Name("权限 ID") permission: Permission, + ) { target.denyPermission(permission, true) sendMessage("OK") } + @Description("查看被授权权限列表") @SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp") - public suspend fun CommandSender.permittedPermissions(target: PermitteeId) { + public suspend fun CommandSender.permittedPermissions( + @Name("被许可人 ID") target: PermitteeId, + ) { val grantedPermissions = target.getPermittedPermissions() sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() }) } + @Description("查看所有权限列表") @SubCommand("listPermissions", "lp") public suspend fun CommandSender.listPermissions() { sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() }) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt index b1178870f..dc73b4d52 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.console.permission import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.permission.parseFromStringImpl import net.mamoe.mirai.console.permission.AbstractPermitteeId.* @@ -107,6 +108,33 @@ public interface PermitteeId { * * - 若指令 A 的权限被授予给 [AnyMember], 那么一个 [ExactMember] 可以执行这个指令. * + * #### 字符串表示 + * + * 当使用 [PermitteeId.asString] 时, 不同的类型的返回值如下表所示. 这些格式也适用于 [BuiltInCommands.PermissionCommand]. + * + * (不区分大小写. 不区分 Bot). + * + * + * | 被许可人类型 | 字符串表示示例 | 备注 | + * |:----------------:|:-----------:|:-------------------------------------| + * | 控制台 | console | | + * | 精确群 | g123456 | 表示群, 而不表示群成员 | + * | 精确好友 | f123456 | 必须通过好友消息 | + * | 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 | + * | 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. | + * | 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 | + * | 任意群 | g* | | + * | 任意群的任意群员 | m* | | + * | 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. | + * | 任意群的任意临时会话 | t* | 必须通过临时会话 | + * | 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 | + * | 任意好友 | f* | | + * | 任意用户 | u* | 任何人在任何环境 | + * | 任意对象 | * | 即任何人, 任何群, 控制台 | + * + * + * #### 关系图 + * * ``` * Console AnyContact * ↑ diff --git a/docs/Permissions.md b/docs/Permissions.md index 3dfb66204..6ba743f00 100644 --- a/docs/Permissions.md +++ b/docs/Permissions.md @@ -108,6 +108,28 @@ interface PermitteeId { 在 [`AbstractPermitteeId`] 查看其子类。 +#### 字符串表示 + +当使用 `PermitteeId.asString` 时, 不同的类型的返回值如下表所示. 这些格式也适用于 [权限指令](#使用内置权限服务指令). +(不区分大小写. 不区分 Bot). + +| 被许可人类型 | 字符串表示示例 | 备注 | +|:----------------:|:-----------:|:-------------------------------------| +| 控制台 | console | | +| 精确群 | g123456 | 表示群, 而不表示群成员 | +| 精确好友 | f123456 | 必须通过好友消息 | +| 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 | +| 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. | +| 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 | +| 任意群 | g* | | +| 任意群的任意群员 | m* | | +| 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. | +| 任意群的任意临时会话 | t* | 必须通过临时会话 | +| 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 | +| 任意好友 | f* | | +| 任意用户 | u* | 任何人在任何环境 | +| 任意对象 | * | 即任何人, 任何群, 控制台 | + ### 获取被许可人 通常使用 `CommandSender.permitteeId`。 @@ -138,4 +160,18 @@ fun Permission.testPermission(PermitteeId): Boolean 如果希望手动注册一个其他用途的权限,使用 `PermissionService.register`。 -**注意**:权限只能在插件 [启用](Plugins.md#启用) 之后才能注册。否则会得到一个异常。 \ No newline at end of file +**注意**:权限只能在插件 [启用](Plugins.md#启用) 之后才能注册。否则会得到一个异常。 + +### 使用内置权限服务指令 + +**根指令**: "/permission", "/perm", "/权限" + +``` +/permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限 +/permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限 +/permission listpermissions 查看所有权限列表 +/permission permit <被许可人 ID> <权限 ID> 授权一个权限 +/permission permittedpermissions <被许可人 ID> 查看被授权权限列表 +``` + +其中, 被许可人 ID 使用 [字符串表示](#字符串表示), 权限 ID 参见 [权限 ID](#权限-id) From 7c8af5d59532c322230cab4b209db81f62be42df Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Wed, 16 Sep 2020 17:18:39 +0800 Subject: [PATCH 007/114] Update MiraiConsoleBuildPlugin.kt (#182) Sem Version --- buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt index 58ef99ac1..b8567aaec 100644 --- a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt @@ -37,7 +37,7 @@ class MiraiConsoleBuildPlugin : Plugin { "Manifest-Version" to "1", "Implementation-Vendor" to "Mamoe Technologies", "Implementation-Title" to target.name.toString(), - "Implementation-Version" to target.version.toString() + "-" + gitVersion + "Implementation-Version" to target.version.toString() + "+" + gitVersion ) } @Suppress("UNCHECKED_CAST") From 63ff44d406bdd550a3147ef624f3d72d19378422 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 16 Sep 2020 17:27:33 +0800 Subject: [PATCH 008/114] Bump dependencies versions --- buildSrc/src/main/kotlin/Versions.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 859bb16b2..53ee2e1a6 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -8,7 +8,7 @@ */ object Versions { - const val core = "1.2.3" + const val core = "1.3.0" const val console = "1.0-M4" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" @@ -28,5 +28,5 @@ object Versions { const val bintray = "1.8.5" const val blockingBridge = "1.0.5" - const val yamlkt = "0.5.2" + const val yamlkt = "0.5.3" } \ No newline at end of file From cd6e78d36c45f69fa874ffb495559afc46e6f77f Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 10:23:29 +0800 Subject: [PATCH 009/114] Add getInstance for front-end implementers --- .../mamoe/mirai/console/MiraiConsoleImplementation.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt index 8e53e4f9c..f4372f198 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -175,6 +175,15 @@ public interface MiraiConsoleImplementation : CoroutineScope { internal lateinit var instance: MiraiConsoleImplementation private val initLock = ReentrantLock() + /** + * 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例 + * + * 必须在 [start] 之后才能使用. + */ + @JvmStatic + @ConsoleFrontEndImplementation + public fun getInstance(): MiraiConsoleImplementation = instance + /** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */ @JvmStatic @ConsoleFrontEndImplementation From 466b067d9fce533f9ade31907c00b37fc1668706 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 10:24:48 +0800 Subject: [PATCH 010/114] Throw IllegalStateException when restarting MiraiConsoleImplementation --- .../kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt index f4372f198..94aee7ae3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -189,6 +189,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { @ConsoleFrontEndImplementation @Throws(MalformedMiraiConsoleImplementationError::class) public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock { + if (::instance.isInitialized) error("Mirai Console is already initialized.") this@Companion.instance = this MiraiConsoleImplementationBridge.doStart() } From dc81835b68372620a5d28b0c56473c150b7c1579 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Thu, 17 Sep 2020 20:52:57 +0800 Subject: [PATCH 011/114] Sem Version (#164) * Sem Version * Review: Add missing logic * Review code - Removed @JvmField - Comments - Fix Compare Logic - Add tests from SemVer.org * Deleted redundant statement * Rename RangeChecker to RangeRequirement * Code Review - Move SemVersion#compareTo to SemVersionInternal#compareInternal - Move top-level functions to companion object - Make SemVersion comparable * KDoc * Update KDoc; fix parseRangeRequirement * Update KDoc * Update comment * Update KDoc * Update KDoc * Typo * Typo --- .../internal/util/SemVersionInternal.kt | 224 ++++++++++++++++ .../mamoe/mirai/console/util/SemVersion.kt | 239 ++++++++++++++++++ .../mirai/console/util/TestSemVersion.kt | 116 +++++++++ 3 files changed, 579 insertions(+) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt create mode 100644 backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt new file mode 100644 index 000000000..e09fd87cc --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -0,0 +1,224 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +/* + * @author Karlatemp + */ + +package net.mamoe.mirai.console.internal.util + +import net.mamoe.mirai.console.util.SemVersion +import kotlin.math.max +import kotlin.math.min + +@Suppress("RegExpRedundantEscape") +internal object SemVersionInternal { + private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() + private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() + private val versionRange = """([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\-\s*([0-9]+(\.[0-9]+)+(|[\-+].+))""".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 fun Collection<*>.dump() { + forEachIndexed { index, value -> + println("$index, $value") + } + } + + @JvmStatic + private fun String.parseRule(): SemVersion.RangeRequirement { + val trimmed = trim() + if (directVersion.matches(trimmed)) { + val parsed = SemVersion.parse(trimmed) + return SemVersion.RangeRequirement { + it.compareTo(parsed) == 0 + } + } + if (versionSelect.matches(trimmed)) { + val regex = ("^" + + trimmed.replace(".", "\\.") + .replace("x", ".+") + + "$" + ).toRegex() + return SemVersion.RangeRequirement { + regex.matches(it.toString()) + } + } + (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range -> + var start = SemVersion.parse(range.groupValues[1]) + var end = SemVersion.parse(range.groupValues[4]) + if (start > end) { + val c = end + end = start + start = c + } + val compareRange = start..end + return SemVersion.RangeRequirement { + it in compareRange + } + } + versionRule.matchEntire(trimmed)?.let { result -> + val operator = result.groupValues[1] + val version = SemVersion.parse(result.groupValues[7]) + return when (operator) { + ">=" -> { + SemVersion.RangeRequirement { it >= version } + } + ">" -> { + SemVersion.RangeRequirement { it > version } + } + "<=" -> { + SemVersion.RangeRequirement { it <= version } + } + "<" -> { + SemVersion.RangeRequirement { it < version } + } + "=" -> { + SemVersion.RangeRequirement { it.compareTo(version) == 0 } + } + else -> throw AssertionError("operator=$operator, version=$version") + } + } + throw UnsupportedOperationException("Cannot parse $this") + } + + private fun SemVersion.RangeRequirement.withRule(rule: String): SemVersion.RangeRequirement { + return object : SemVersion.RangeRequirement { + override fun check(version: SemVersion): Boolean { + return this@withRule.check(version) + } + + override fun toString(): String { + return rule + } + } + } + + @JvmStatic + fun parseRangeRequirement(requirement: String): SemVersion.RangeRequirement { + if (requirement.isBlank()) { + throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") + } + return requirement.split("||").map { + it.parseRule().withRule(it) + }.let { checks -> + if (checks.size == 1) return checks[0] + SemVersion.RangeRequirement { + checks.forEach { rule -> + if (rule.check(it)) return@RangeRequirement true + } + return@RangeRequirement false + }.withRule(requirement) + } + } + + @JvmStatic + fun SemVersion.compareInternal(other: SemVersion): Int { + // ignored metadata in comparing + + // If $this equals $other (without metadata), + // return same. + if (other.mainVersion.contentEquals(mainVersion) && identifier == other.identifier) { + return 0 + } + fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 } + + // Compare main-version + for (index in 0 until (max(mainVersion.size, other.mainVersion.size))) { + val result = mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index)) + if (result != 0) return result + } + // If main-versions are same. + var identifier0 = identifier + var identifier1 = other.identifier + // If anyone doesn't have the identifier... + if (identifier0 == null || identifier1 == null) { + return when (identifier0) { + identifier1 -> { // null == null + // Nobody has identifier + 0 + } + null -> { + // $other has identifier, but $this don't have identifier + // E.g: + // this = 1.0.0 + // other = 1.0.0-dev + 1 + } + // It is the opposite of the above. + else -> -1 + } + } + fun String.getSafe(index: Int) = getOrElse(index) { ' ' } + + // ignored same prefix + fun getSameSize(s1: String, s2: String): Int { + val size = min(s1.length, s2.length) + // 1.0-RC19 -> 19 + // 1.0-RC107 -> 107 + var realSameSize = 0 + for (index in 0 until size) { + if (s1[index] != s2[index]) { + return realSameSize + } else { + if (!s1[index].isDigit()) { + realSameSize = index + 1 + } + } + } + return realSameSize + } + + // We ignore the same parts. Because we only care about the differences. + // E.g: + // 1.0-RC1 -> 1 + // 1.0-RC2 -> 2 + val ignoredSize = getSameSize(identifier0, identifier1) + identifier0 = identifier0.substring(ignoredSize) + identifier1 = identifier1.substring(ignoredSize) + // Multi-chunk comparing + val chunks0 = identifier0.split('-', '.', '_') + val chunks1 = identifier1.split('-', '.', '_') + chunkLoop@ for (index in 0 until (max(chunks0.size, chunks1.size))) { + val value0 = chunks0.getOrNull(index) + val value1 = chunks1.getOrNull(index) + // Any chunk is null + if (value0 == null || value1 == null) { + // value0 == null && value1 == null is impossible + return if (value0 == null) { + // E.g: + // value0 = 1.0-RC-dev + // value1 = 1.0-RC-dev-1 + -1 + } else { + // E.g: + // value0 = 1.0-RC-dev-1 + // value1 = 1.0-RC-dev + 1 + } + } + try { + val result = value0.toInt().compareTo(value1.toInt()) + if (result != 0) { + return result + } + continue@chunkLoop + } catch (ignored: NumberFormatException) { + } + // compare chars + for (index0 in 0 until (max(value0.length, value1.length))) { + val result = value0.getSafe(index0).compareTo(value1.getSafe(index0)) + if (result != 0) + return result + } + } + return 0 + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt new file mode 100644 index 000000000..02a6f9587 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -0,0 +1,239 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +/* + * @author Karlatemp + */ + +package net.mamoe.mirai.console.util + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import net.mamoe.mirai.console.internal.util.SemVersionInternal +import net.mamoe.mirai.console.util.SemVersion.Companion.equals + +/** + * 语义化版本支持 + * + * 在阅读此文件前, 请先阅读 https://semver.org/lang/zh-CN/ + * 该文档说明了语义化版本是什么, 此文件不再过多描述 + * + * ---- + * + * 这是一个例子 `1.0.0-M4+c25733b8` + * + * 将会解析出三个内容, mainVersion(核心版本号), identifier(先行版本号) 和 metadata(元数据). + * + * 对这个例子进行解析会得到 + * ``` + * SemVersion( + * mainVersion = IntArray [1, 0, 0], + * identifier = "M4" + * metadata = "c25733b8" + * ) + * ``` + * 其中 identifier 和 metadata 都是可选的, 该实现对于 mainVersion 的最大长度不作出限制, + * 也建议 mainVersion 的长度不要过长或过短 + * 但是必须至少拥有两位及以上的版本描述符, (即必须拥有主版本号和次版本号). + * + * 比如 `1-M4` 是不合法的, 但是 `1.0-M4` 是合法的 + * + */ +@Serializable +public data class SemVersion internal constructor( + /** 核心版本号, 至少包含一个主版本号和一个次版本号 */ + public val mainVersion: IntArray, + /** 先行版本号识别符 */ + public val identifier: String? = null, + /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ + public val metadata: String? = null +) : Comparable { + /** + * 一条依赖规则 + * @see [parseRangeRequirement] + */ + public fun interface RangeRequirement { + /** 在 [version] 满足此要求时返回 true */ + public fun check(version: SemVersion): Boolean + } + + public companion object { + /** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */ + @JvmStatic + private fun String.parseMainVersion(): IntArray = + split('.').map { it.toInt() }.toIntArray() + + /** + * 解析一个版本号, 将会返回一个 [SemVersion], + * 如果发生解析错误将会抛出一个 [IllegalArgumentException] 或者 [NumberFormatException] + * + * 对于版本号的组成, 我们有以下规定: + * - 必须包含主版本号和次版本号 + * - 存在 先行版本号 的时候 先行版本号 不能为空 + * - 存在 元数据 的时候 元数据 不能为空 + * + * 注意情况: + * - 第一个 `+` 之后的所有内容全部识别为元数据 + * - `1.0+METADATA-M4`, metadata="METADATA-M4" + */ + @Throws(IllegalArgumentException::class, NumberFormatException::class) + @JvmStatic + public fun parse(version: String): SemVersion { + var mainVersionEnd: Int = 0 + kotlin.run { + val iterator = version.iterator() + while (iterator.hasNext()) { + val next = iterator.next() + if (next == '-' || next == '+') { + break + } + mainVersionEnd++ + } + } + var identifier: String? = null + var metadata: String? = null + if (mainVersionEnd != version.length) { + when (version[mainVersionEnd]) { + '-' -> { + val metadataSplitter = version.indexOf('+', startIndex = mainVersionEnd) + if (metadataSplitter == -1) { + identifier = version.substring(mainVersionEnd + 1) + } else { + identifier = version.substring(mainVersionEnd + 1, metadataSplitter) + metadata = version.substring(metadataSplitter + 1) + } + } + '+' -> { + metadata = version.substring(mainVersionEnd + 1) + } + } + } + return SemVersion( + mainVersion = version.substring(0, mainVersionEnd).also { mainVersion -> + if (mainVersion.indexOf('.') == -1) { + throw IllegalArgumentException("$mainVersion must has more than one label") + } + if (mainVersion.last() == '.') { + throw IllegalArgumentException("Version string cannot end-with `.`") + } + }.parseMainVersion(), + identifier = identifier?.also { + if (it.isBlank()) { + throw IllegalArgumentException("The identifier cannot be blank.") + } + }, + metadata = metadata?.also { + if (it.isBlank()) { + throw IllegalArgumentException("The metadata cannot be blank.") + } + } + ) + } + + /** + * 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException] + * + * 对于一条规则, 有以下方式可选 + * + * - `1.0.0-M4` 要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本 + * - `1.x` 要求 1.x 版本 + * - `1.0.0 - 1.2.0` 要求 1.0.0 到 1.2.0 的任意版本, 注意 `-` 两边必须要有空格 + * - `[1.0.0, 1.2.0]` 与 `1.0.0 - 1.2.0` 一致 + * - `> 1.0.0-RC` 要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC + * - `>= 1.0.0-RC` 要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC + * - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC + * - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC + * + * 对于多个规则, 也允许使用 `||` 拼接在一起. + * 例如: + * - `1.x || 2.x || 3.0` + * - `<= 0.5.3 || >= 1.0.0` + * + * 特别注意: + * - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查 + * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号 + */ + @Throws(IllegalArgumentException::class) + @JvmStatic + public fun parseRangeRequirement(requirement: String): RangeRequirement { + return SemVersionInternal.parseRangeRequirement(requirement) + } + + /** @see [RangeRequirement.check] */ + @JvmStatic + public fun RangeRequirement.check(version: String): Boolean = check(parse(version)) + + /** + * 当满足 [requirement] 时返回 true, 否则返回 false + */ + @JvmStatic + public fun SemVersion.satisfies(requirement: RangeRequirement): Boolean = requirement.check(this) + + /** for Kotlin only */ + @JvmStatic + @JvmSynthetic + public operator fun RangeRequirement.contains(version: SemVersion): Boolean = check(version) + + /** for Kotlin only */ + @JvmStatic + @JvmSynthetic + public operator fun RangeRequirement.contains(version: String): Boolean = check(version) + } + + @Transient + private var toString: String? = null // For cache. + override fun toString(): String { + return toString ?: kotlin.run { + buildString { + mainVersion.joinTo(this, ".") + identifier?.let { identifier -> + append('-') + append(identifier) + } + metadata?.let { metadata -> + append('+') + append(metadata) + } + }.also { toString = it } + } + } + + /** + * 将 [SemVersion] 转为 Kotlin data class 风格的 [String] + */ + public fun toStructuredString(): String { + return "SemVersion(mainVersion=${mainVersion.contentToString()}, identifier=$identifier, metadata=$metadata)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SemVersion + + return compareTo(other) == 0 && other.identifier == identifier && other.metadata == metadata + } + + override fun hashCode(): Int { + var result = mainVersion.contentHashCode() + result = 31 * result + (identifier?.hashCode() ?: 0) + result = 31 * result + (metadata?.hashCode() ?: 0) + return result + } + + /** + * Compares this object with the specified object for order. Returns zero if this object is equal + * to the specified [other] object, a negative number if it's less than [other], or a positive number + * if it's greater than [other]. + */ + public override operator fun compareTo(other: SemVersion): Int { + return SemVersionInternal.run { compareInternal(other) } + } +} diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt new file mode 100644 index 000000000..36923773b --- /dev/null +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -0,0 +1,116 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +/* + * @author Karlatemp + */ + +package net.mamoe.mirai.console.util + +import net.mamoe.mirai.console.util.SemVersion.Companion.check +import org.junit.jupiter.api.Test + +internal class TestSemVersion { + @Test + internal fun testCompare() { + fun String.sem(): SemVersion = SemVersion.parse(this) + assert("1.0".sem() < "1.0.1".sem()) + assert("1.0.0".sem() == "1.0".sem()) + assert("1.1".sem() > "1.0.0.1".sem()) + assert("1.0-M4".sem() < "1.0-M5".sem()) + assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem()) + assert("1.0-M5-dev-79".sem() < "1.0-M5-dev-7001".sem()) + assert("1.0-M6".sem() > "1.0-M5-dev-15".sem()) + assert("1.0-RC".sem() > "1.0-M5-dev-15".sem()) + assert("1.0-RC2".sem() > "1.0-RC".sem()) + // example on semver + // 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 + assert("1.0.0-alpha".sem() < "1.0.0-alpha.1".sem()) + assert("1.0.0-alpha.1".sem() < "1.0.0-alpha.beta".sem()) + assert("1.0.0-alpha.beta".sem() < "1.0.0-beta".sem()) + assert("1.0.0-beta".sem() < "1.0.0-beta.2".sem()) + assert("1.0.0-beta.2".sem() < "1.0.0-beta.11".sem()) + assert("1.0.0-beta.11".sem() < "1.0.0-rc.1".sem()) + assert("1.0.0-rc.1".sem() < "1.0.0".sem()) + } + + @Test + internal fun testRequirement() { + fun SemVersion.RangeRequirement.assert(version: String): SemVersion.RangeRequirement { + assert(check(version)) { version } + return this + } + + fun SemVersion.RangeRequirement.assertFalse(version: String): SemVersion.RangeRequirement { + assert(!check(version)) { version } + return this + } + SemVersion.parseRangeRequirement("1.0") + .assert("1.0").assert("1.0.0") + .assert("1.0.0.0") + .assertFalse("1.1.0").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("1.x") + .assert("1.0").assert("1.1") + .assert("1.5").assert("1.14514") + .assertFalse("2.33") + SemVersion.parseRangeRequirement("2.0 || 1.2.x") + .assert("2.0").assert("2.0.0") + .assertFalse("2.1").assertFalse("2.0.0.1") + .assert("1.2.5").assert("1.2.0").assertFalse("1.2") + .assertFalse("1.0.0") + SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919.810") + .assert("1.0.0") + .assert("114.514").assert("114.514.1919.810") + .assertFalse("0.0.1") + .assertFalse("4444.4444") + SemVersion.parseRangeRequirement("[1.0.0, 19190.0]") + .assert("1.0.0").assertFalse("0.1.0") + .assert("19190.0").assertFalse("19198.10") + SemVersion.parseRangeRequirement(" >= 1.0.0") + .assert("1.0.0") + .assert("114.514.1919.810") + .assertFalse("0.0.0") + .assertFalse("0.98774587") + SemVersion.parseRangeRequirement("> 1.0.0") + .assertFalse("1.0.0") + kotlin.runCatching { SemVersion.parseRangeRequirement("WPOXAXW") } + .onSuccess { assert(false) } + + } + + @Test + internal fun testSemVersionParsing() { + fun String.check() { + val sem = SemVersion.parse(this) + assert(this == sem.toString()) { "$this != $sem" } + } + + fun String.checkInvalid() { + kotlin.runCatching { SemVersion.parse(this) } + .onSuccess { assert(false) { "$this not a invalid sem-version" } } + .onFailure { println("$this - $it") } + } + "0.0".check() + "1.0.0".check() + "1.2.3.4.5.6.7.8".check() + "5555.0-A".check() + "5555.0-A+METADATA".check() + "5555.0+METADATA".check() + "987.0+wwwxx-wk".check() + "NOT.NUMBER".checkInvalid() + "0".checkInvalid() + "".checkInvalid() + "1.".checkInvalid() + "0.1-".checkInvalid() + "1.9+".checkInvalid() + "5.1+68-7".check() + "5.1+68-".check() + } +} \ No newline at end of file From 287b4b2995f51b4aab46824404c12121b900b7a3 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 21:24:13 +0800 Subject: [PATCH 012/114] Group mirai-console-intellij and mirai-console-gradle into 'tools' --- settings.gradle.kts | 4 ++-- .../gradle-plugin}/build.gradle.kts | 0 .../intellij-plugin}/build.gradle.kts | 0 .../intellij-plugin}/libs/ide-common.jar | Bin .../intellij-plugin}/run/projects/.gitignore | 0 .../run/projects/test-project/build.gradle.kts | 0 .../run/projects/test-project/gradle.properties | 0 .../test-project/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../run/projects/test-project/gradlew | 0 .../run/projects/test-project/gradlew.bat | 0 .../run/projects/test-project/settings.gradle.kts | 0 .../kotlin/org/example/myplugin/MyPluginMain.kt | 0 .../kotlin/org/example/myplugin/MySimpleCommand.kt | 0 .../test-project/src/test/kotlin/RunConsole.kt | 0 .../console/intellij/IDEContainerContributor.kt | 3 ++- .../net/mamoe/mirai/console/intellij/Icons.kt | 0 .../diagnostics/PluginDescriptionChecker.kt | 5 ++--- .../marker/CommandDeclarationLineMarkerProvider.kt | 0 .../line/marker/PluginMainLineMarkerProvider.kt | 0 .../mirai/console/intellij/line/marker/util.kt | 0 .../mirai/console/intellij/resolve/psiResolve.kt | 0 .../src/main/resources/META-INF/plugin.xml | 0 .../src/main/resources/icons/commandDeclaration.svg | 0 .../main/resources/icons/pluginMainDeclaration.png | Bin 25 files changed, 6 insertions(+), 6 deletions(-) rename {gradle-plugin => tools/gradle-plugin}/build.gradle.kts (100%) rename {intellij-plugin => tools/intellij-plugin}/build.gradle.kts (100%) rename {intellij-plugin => tools/intellij-plugin}/libs/ide-common.jar (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/.gitignore (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/build.gradle.kts (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/gradle.properties (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/gradlew (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/gradlew.bat (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/settings.gradle.kts (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/run/projects/test-project/src/test/kotlin/RunConsole.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt (87%) rename {intellij-plugin => tools/intellij-plugin}/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt (100%) rename intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt => tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt (87%) rename {intellij-plugin => tools/intellij-plugin}/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/resources/META-INF/plugin.xml (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/resources/icons/commandDeclaration.svg (100%) rename {intellij-plugin => tools/intellij-plugin}/src/main/resources/icons/pluginMainDeclaration.png (100%) diff --git a/settings.gradle.kts b/settings.gradle.kts index e1b5cfd3a..aaf891a63 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,8 +20,8 @@ fun includeProject(projectPath: String, path: String? = null) { includeProject(":mirai-console", "backend/mirai-console") includeProject(":mirai-console.codegen", "backend/codegen") includeProject(":mirai-console-pure", "frontend/mirai-console-pure") -includeProject(":mirai-console-intellij", "intellij-plugin") -includeProject(":mirai-console-gradle", "gradle-plugin") +includeProject(":mirai-console-intellij", "tools/intellij-plugin") +includeProject(":mirai-console-gradle", "tools/gradle-plugin") @Suppress("ConstantConditionIf") if (!disableOldFrontEnds) { diff --git a/gradle-plugin/build.gradle.kts b/tools/gradle-plugin/build.gradle.kts similarity index 100% rename from gradle-plugin/build.gradle.kts rename to tools/gradle-plugin/build.gradle.kts diff --git a/intellij-plugin/build.gradle.kts b/tools/intellij-plugin/build.gradle.kts similarity index 100% rename from intellij-plugin/build.gradle.kts rename to tools/intellij-plugin/build.gradle.kts diff --git a/intellij-plugin/libs/ide-common.jar b/tools/intellij-plugin/libs/ide-common.jar similarity index 100% rename from intellij-plugin/libs/ide-common.jar rename to tools/intellij-plugin/libs/ide-common.jar diff --git a/intellij-plugin/run/projects/.gitignore b/tools/intellij-plugin/run/projects/.gitignore similarity index 100% rename from intellij-plugin/run/projects/.gitignore rename to tools/intellij-plugin/run/projects/.gitignore diff --git a/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts similarity index 100% rename from intellij-plugin/run/projects/test-project/build.gradle.kts rename to tools/intellij-plugin/run/projects/test-project/build.gradle.kts diff --git a/intellij-plugin/run/projects/test-project/gradle.properties b/tools/intellij-plugin/run/projects/test-project/gradle.properties similarity index 100% rename from intellij-plugin/run/projects/test-project/gradle.properties rename to tools/intellij-plugin/run/projects/test-project/gradle.properties diff --git a/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar b/tools/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar rename to tools/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.jar diff --git a/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties b/tools/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties rename to tools/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties diff --git a/intellij-plugin/run/projects/test-project/gradlew b/tools/intellij-plugin/run/projects/test-project/gradlew similarity index 100% rename from intellij-plugin/run/projects/test-project/gradlew rename to tools/intellij-plugin/run/projects/test-project/gradlew diff --git a/intellij-plugin/run/projects/test-project/gradlew.bat b/tools/intellij-plugin/run/projects/test-project/gradlew.bat similarity index 100% rename from intellij-plugin/run/projects/test-project/gradlew.bat rename to tools/intellij-plugin/run/projects/test-project/gradlew.bat diff --git a/intellij-plugin/run/projects/test-project/settings.gradle.kts b/tools/intellij-plugin/run/projects/test-project/settings.gradle.kts similarity index 100% rename from intellij-plugin/run/projects/test-project/settings.gradle.kts rename to tools/intellij-plugin/run/projects/test-project/settings.gradle.kts diff --git a/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt similarity index 100% rename from intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt rename to tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt diff --git a/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt similarity index 100% rename from intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt rename to tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt diff --git a/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt b/tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt similarity index 100% rename from intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt rename to tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt similarity index 87% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt index ef7231786..7e023b444 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.intellij +import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useInstance import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -20,6 +21,6 @@ class IDEContainerContributor : StorageComponentContainerContributor { platform: org.jetbrains.kotlin.platform.TargetPlatform, moduleDescriptor: ModuleDescriptor, ) { - container.useInstance(MiraiConsoleDeclarationChecker()) + container.useInstance(PluginDescriptionChecker()) } } \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt similarity index 100% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/Icons.kt diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt similarity index 87% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index f2d99b213..83a52f8ca 100644 --- a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/MiraiConsoleDeclarationChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -7,19 +7,18 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.intellij +package net.mamoe.mirai.console.intellij.diagnostics import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext -class MiraiConsoleDeclarationChecker : DeclarationChecker { +class PluginDescriptionChecker : DeclarationChecker { override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, ) { - } } \ No newline at end of file diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt similarity index 100% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt similarity index 100% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt similarity index 100% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt diff --git a/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt similarity index 100% rename from intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt diff --git a/intellij-plugin/src/main/resources/META-INF/plugin.xml b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml similarity index 100% rename from intellij-plugin/src/main/resources/META-INF/plugin.xml rename to tools/intellij-plugin/src/main/resources/META-INF/plugin.xml diff --git a/intellij-plugin/src/main/resources/icons/commandDeclaration.svg b/tools/intellij-plugin/src/main/resources/icons/commandDeclaration.svg similarity index 100% rename from intellij-plugin/src/main/resources/icons/commandDeclaration.svg rename to tools/intellij-plugin/src/main/resources/icons/commandDeclaration.svg diff --git a/intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png b/tools/intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png similarity index 100% rename from intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png rename to tools/intellij-plugin/src/main/resources/icons/pluginMainDeclaration.png From f37f059634541f31a8385dfbe07e501e85fde088 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 21:39:34 +0800 Subject: [PATCH 013/114] Introduce compiler-common module --- settings.gradle.kts | 1 + tools/compiler-common/README.md | 5 ++ tools/compiler-common/build.gradle.kts | 75 +++++++++++++++++++ .../diagnostics/MiraiConsoleErrors.java | 31 ++++++++ .../MiraiConsoleErrorsRendering.kt | 33 ++++++++ tools/gradle-plugin/README.md | 2 + tools/intellij-plugin/README.md | 7 ++ tools/intellij-plugin/build.gradle.kts | 2 + 8 files changed, 156 insertions(+) create mode 100644 tools/compiler-common/README.md create mode 100644 tools/compiler-common/build.gradle.kts create mode 100644 tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java create mode 100644 tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt create mode 100644 tools/gradle-plugin/README.md create mode 100644 tools/intellij-plugin/README.md diff --git a/settings.gradle.kts b/settings.gradle.kts index aaf891a63..27b9b3fc2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,7 @@ fun includeProject(projectPath: String, path: String? = null) { includeProject(":mirai-console", "backend/mirai-console") includeProject(":mirai-console.codegen", "backend/codegen") includeProject(":mirai-console-pure", "frontend/mirai-console-pure") +includeProject(":mirai-console-compiler-common", "tools/compiler-common") includeProject(":mirai-console-intellij", "tools/intellij-plugin") includeProject(":mirai-console-gradle", "tools/gradle-plugin") diff --git a/tools/compiler-common/README.md b/tools/compiler-common/README.md new file mode 100644 index 000000000..a3e4973fb --- /dev/null +++ b/tools/compiler-common/README.md @@ -0,0 +1,5 @@ +# mirai-console-compiler-common + +Mirai Console 编译器后端通用模块. + +## \ No newline at end of file diff --git a/tools/compiler-common/build.gradle.kts b/tools/compiler-common/build.gradle.kts new file mode 100644 index 000000000..fb6a5e172 --- /dev/null +++ b/tools/compiler-common/build.gradle.kts @@ -0,0 +1,75 @@ +@file:Suppress("UnusedImport") + +plugins { + kotlin("jvm") + id("java") + `maven-publish` + id("com.jfrog.bintray") +} + +repositories { + maven("http://maven.aliyun.com/nexus/content/groups/public/") +} + +version = Versions.console +description = "Mirai Console compiler common" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" +} + +kotlin { + sourceSets.all { + target.compilations.all { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + //useIR = true + } + } + languageSettings.apply { + progressiveMode = true + + useExperimentalAnnotation("kotlin.Experimental") + useExperimentalAnnotation("kotlin.RequiresOptIn") + + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") + useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") + useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") + useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi") + useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") + useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") + useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") + useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi") + useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi") + } + } +} + +dependencies { + api("org.jetbrains:annotations:19.0.0") + api(kotlinx("coroutines-jdk8", Versions.coroutines)) + + compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") + compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") + compileOnly(files("libs/ide-common.jar")) + + testApi(kotlin("test")) + testApi(kotlin("test-junit5")) + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") +} + +tasks { + "test"(Test::class) { + useJUnitPlatform() + } +} + +// setupPublishing("mirai-console-intellij") \ No newline at end of file diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java new file mode 100644 index 000000000..2b65a3bd8 --- /dev/null +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -0,0 +1,31 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.compiler.common.diagnostics; + +import com.intellij.psi.PsiElement; +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; +import org.jetbrains.kotlin.diagnostics.Errors; + +import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; + +public interface MiraiConsoleErrors { + DiagnosticFactory1 ILLEGAL_PLUGIN_ID = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 ILLEGAL_PLUGIN_NAME = DiagnosticFactory1.create(ERROR); + + @Deprecated + Object _init = new Object() { + { + Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages( + MiraiConsoleErrors.class, + MiraiConsoleErrorsRendering.INSTANCE + ); + } + }; +} diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt new file mode 100644 index 000000000..8eb25bc37 --- /dev/null +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -0,0 +1,33 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +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_NAME +import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages +import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap +import org.jetbrains.kotlin.diagnostics.rendering.Renderers + +object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { + private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply { + put( + ILLEGAL_PLUGIN_ID, + "Illegal plugin id: '{0}'", + Renderers.STRING + ) + put( + ILLEGAL_PLUGIN_NAME, + "Illegal plugin name: '{0}'", + Renderers.STRING + ) + } + + override fun getMap() = MAP +} diff --git a/tools/gradle-plugin/README.md b/tools/gradle-plugin/README.md new file mode 100644 index 000000000..78e0e7f29 --- /dev/null +++ b/tools/gradle-plugin/README.md @@ -0,0 +1,2 @@ +# mirai-console-gradle + diff --git a/tools/intellij-plugin/README.md b/tools/intellij-plugin/README.md new file mode 100644 index 000000000..da5b520e0 --- /dev/null +++ b/tools/intellij-plugin/README.md @@ -0,0 +1,7 @@ +# mirai-console-intellij + +IntelliJ 平台的 Mirai Console 开发插件 + +## 功能 + +### 诊断 \ No newline at end of file diff --git a/tools/intellij-plugin/build.gradle.kts b/tools/intellij-plugin/build.gradle.kts index ace7adb38..3520633be 100644 --- a/tools/intellij-plugin/build.gradle.kts +++ b/tools/intellij-plugin/build.gradle.kts @@ -87,6 +87,8 @@ dependencies { api("org.jetbrains:annotations:19.0.0") api(kotlinx("coroutines-jdk8", Versions.coroutines)) + api(project(":mirai-console-compiler-common")) + compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") compileOnly(files("libs/ide-common.jar")) From c1ea2d94eec43a31ab257908305af127a81c0573 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 21:51:24 +0800 Subject: [PATCH 014/114] Move compiler common to compiler-common module --- tools/compiler-common/build.gradle.kts | 1 - .../common/resolve/MiraiConsoleTypes.kt | 27 +++++++++++++++++++ .../diagnostics/PluginDescriptionChecker.kt | 6 +++++ .../marker/PluginMainLineMarkerProvider.kt | 4 +-- .../console/intellij/line/marker/util.kt | 8 ------ .../resolve/{psiResolve.kt => resolveIdea.kt} | 9 +++---- 6 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt rename tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/{psiResolve.kt => resolveIdea.kt} (74%) diff --git a/tools/compiler-common/build.gradle.kts b/tools/compiler-common/build.gradle.kts index fb6a5e172..37295d0be 100644 --- a/tools/compiler-common/build.gradle.kts +++ b/tools/compiler-common/build.gradle.kts @@ -57,7 +57,6 @@ dependencies { compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") compileOnly("org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}") - compileOnly(files("libs/ide-common.jar")) testApi(kotlin("test")) testApi(kotlin("test-junit5")) diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt new file mode 100644 index 000000000..10b1e5939 --- /dev/null +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.compiler.common.resolve + +import org.jetbrains.kotlin.name.FqName + +/////////////////////////////////////////////////////////////////////////// +// Command +/////////////////////////////////////////////////////////////////////////// + +val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") +val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") + +/////////////////////////////////////////////////////////////////////////// +// Plugin +/////////////////////////////////////////////////////////////////////////// + +val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") +val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") +val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 83a52f8ca..658a13eef 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -14,11 +14,17 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +/** + * Checks: + * - plugin id + * - plugin name + */ class PluginDescriptionChecker : DeclarationChecker { override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, ) { + } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt index 866d22378..8f5d43708 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt @@ -16,8 +16,8 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import com.intellij.util.castSafelyTo +import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME import net.mamoe.mirai.console.intellij.Icons -import net.mamoe.mirai.console.intellij.resolve.Plugin_FQ_NAME import org.jetbrains.kotlin.nj2k.postProcessing.resolve import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtConstructor @@ -31,7 +31,7 @@ class PluginMainLineMarkerProvider : LineMarkerProvider { element.parents.filterIsInstance().firstOrNull() ?: return null val kotlinPluginClass = element.resolve().castSafelyTo>()?.parent?.castSafelyTo() ?: return null - if (kotlinPluginClass.allSuperNames.none { it == Plugin_FQ_NAME }) return null + if (kotlinPluginClass.allSuperNames.none { it == PLUGIN_FQ_NAME }) return null return Info(getElementForLineMark(objectDeclaration)) } diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt index d75f2e05b..a407e5a6a 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt @@ -11,8 +11,6 @@ package net.mamoe.mirai.console.intellij.line.marker import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import com.intellij.psi.PsiReferenceExpression -import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.nj2k.postProcessing.resolve @@ -31,12 +29,6 @@ internal inline fun PsiElement.findParent(): E? = this.parents.filte internal val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } -fun PsiReferenceExpression.hasBridgeCalls(): Boolean { - val resolved = this.resolve() as? KtLightMethod ?: return false - - TODO() -} - val PsiElement.parents: Sequence get() { val seed = if (this is PsiFile) null else parent diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt similarity index 74% rename from tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index c72204ade..6fba5b74b 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/psiResolve.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -9,14 +9,11 @@ package net.mamoe.mirai.console.intellij.resolve +import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME +import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME import net.mamoe.mirai.console.intellij.line.marker.hasAnnotation -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtNamedFunction -val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") -val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") - -val Plugin_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") /** * For CompositeCommand.SubCommand @@ -29,4 +26,4 @@ fun KtNamedFunction.isCompositeCommandSubCommand(): Boolean = this.hasAnnotation fun KtNamedFunction.isSimpleCommandHandler(): Boolean = this.hasAnnotation(SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME) fun KtNamedFunction.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean = - this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand() \ No newline at end of file + this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand() From 7bcef7997a1acb743ed2b3f25b345e2d2cfcf059 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 22:07:45 +0800 Subject: [PATCH 015/114] Introduce ResolveContext for resolve --- .../console/compiler/common/ResolveContext.kt | 36 ++++++++++++++++ .../plugin/jvm/JvmPluginDescription.kt | 43 ++++++++++++------- 2 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt new file mode 100644 index 000000000..b020dbd7c --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.console.compiler.common + +import net.mamoe.mirai.console.util.ConsoleExperimentalApi + +/** + * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断. + */ +@ConsoleExperimentalApi +@Target(AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.PROPERTY, + AnnotationTarget.FIELD, + AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.SOURCE) +public annotation class ResolveContext( + val kind: Kind, +) { + /** + * 元素数量可能在任意时间被改动 + */ + public enum class Kind { + PLUGIN_ID, + PLUGIN_NAME, + PLUGIN_VERSION, + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 5517ace42..a4e865b81 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -12,6 +12,8 @@ package net.mamoe.mirai.console.plugin.jvm 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.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.description.VersionRequirement @@ -37,15 +39,15 @@ public interface JvmPluginDescription : PluginDescription { /** * @see [PluginDescription.id] */ - id: String, + @ResolveContext(PLUGIN_ID) id: String, /** * @see [PluginDescription.version] */ - version: String, + @ResolveContext(PLUGIN_VERSION) version: String, /** * @see [PluginDescription.name] */ - name: String = id, + @ResolveContext(PLUGIN_NAME) name: String = id, block: JvmPluginDescriptionBuilder.() -> Unit = {}, ): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build() @@ -60,15 +62,15 @@ public interface JvmPluginDescription : PluginDescription { /** * @see [PluginDescription.id] */ - id: String, + @ResolveContext(PLUGIN_ID) id: String, /** * @see [PluginDescription.version] */ - version: Semver, + @ResolveContext(PLUGIN_VERSION) version: Semver, /** * @see [PluginDescription.name] */ - name: String = id, + @ResolveContext(PLUGIN_NAME) name: String = id, block: JvmPluginDescriptionBuilder.() -> Unit = {}, ): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build() } @@ -102,7 +104,10 @@ constructor( private var version: Semver, ) { @Suppress("DEPRECATION_ERROR") - public constructor(name: String, version: String) : this(name, Semver(version, Semver.SemverType.LOOSE)) + public constructor( + @ResolveContext(PLUGIN_NAME) id: String, + @ResolveContext(PLUGIN_VERSION) version: String, + ) : this(id, Semver(version, Semver.SemverType.LOOSE)) private var name: String = id private var author: String = "" @@ -110,19 +115,22 @@ constructor( private var dependencies: MutableSet = mutableSetOf() @ILoveKuriyamaMiraiForever - public fun name(value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() } + public fun name(@ResolveContext(PLUGIN_NAME) value: String): JvmPluginDescriptionBuilder = + apply { this.name = value.trim() } @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) @ILoveKuriyamaMiraiForever - public fun version(value: String): JvmPluginDescriptionBuilder = + public fun version(@ResolveContext(PLUGIN_VERSION) value: String): JvmPluginDescriptionBuilder = apply { this.version = Semver(value, Semver.SemverType.LOOSE) } @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) @ILoveKuriyamaMiraiForever - public fun version(value: Semver): JvmPluginDescriptionBuilder = apply { this.version = value } + public fun version(@ResolveContext(PLUGIN_VERSION) value: Semver): JvmPluginDescriptionBuilder = + apply { this.version = value } @ILoveKuriyamaMiraiForever - public fun id(value: String): JvmPluginDescriptionBuilder = apply { this.id = value.trim() } + public fun id(@ResolveContext(PLUGIN_ID) value: String): JvmPluginDescriptionBuilder = + apply { this.id = value.trim() } @ILoveKuriyamaMiraiForever public fun author(value: String): JvmPluginDescriptionBuilder = apply { this.author = value.trim() } @@ -151,7 +159,7 @@ constructor( */ @ILoveKuriyamaMiraiForever public fun dependsOn( - pluginId: String, + @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, versionRequirement: VersionRequirement, ): JvmPluginDescriptionBuilder = apply { @@ -165,7 +173,7 @@ constructor( */ @ILoveKuriyamaMiraiForever public fun dependsOn( - pluginId: String, + @ResolveContext(PLUGIN_ID) pluginId: String, versionRequirement: VersionRequirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, versionRequirement, false)) @@ -178,7 +186,7 @@ constructor( */ @ILoveKuriyamaMiraiForever public fun dependsOn( - pluginId: String, + @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, null, isOptional)) @@ -199,7 +207,7 @@ constructor( */ @ILoveKuriyamaMiraiForever public fun dependsOn( - pluginId: String, + @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, versionRequirement: VersionRequirement.Builder.() -> VersionRequirement, ): JvmPluginDescriptionBuilder = @@ -214,10 +222,13 @@ constructor( public fun build(): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies) + /** + * 标注一个 [JvmPluginDescription] DSL + */ @Suppress("SpellCheckingInspection") @Retention(AnnotationRetention.SOURCE) @DslMarker - private annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5 + internal annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5 } /** From d97e79d6a400cbaa0b2a17ab78d48974c3bce101 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 17 Sep 2020 22:20:48 +0800 Subject: [PATCH 016/114] Configure .editorconfig --- .editorconfig | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2255d9e4d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +[*.{kt, kts}] +max_line_length = 160 +tab_width = 4 +ij_continuation_indent_size = 4 +indent_size = 4 \ No newline at end of file From 3fc2d7f5c1fa3b2194bca3f87b64fef84a66aa7a Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 00:21:05 +0800 Subject: [PATCH 017/114] Diagnostics and resolve utilities --- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../common/resolve/MiraiConsoleTypes.kt | 33 ++++ .../compiler/common/resolve/resolveCommon.kt | 16 ++ .../console/compiler/common/utilCommon.kt | 31 ++++ .../projects/test-project/build.gradle.kts | 4 +- .../org/example/myplugin/MyPluginMain.kt | 150 +----------------- .../diagnostics/PluginDescriptionChecker.kt | 97 ++++++++++- .../intellij/diagnostics/diagnosticsUtil.kt | 31 ++++ .../CommandDeclarationLineMarkerProvider.kt | 1 + .../marker/PluginMainLineMarkerProvider.kt | 3 + .../console/intellij/line/marker/util.kt | 51 ------ .../console/intellij/resolve/resolveIdea.kt | 107 ++++++++++++- 12 files changed, 322 insertions(+), 204 deletions(-) create mode 100644 tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt create mode 100644 tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt create mode 100644 tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt delete mode 100644 tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 53ee2e1a6..e61e28505 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-M4" + const val console = "1.0-RC-dev-1" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" const val consolePure = console diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt index 10b1e5939..48ba53148 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt @@ -9,7 +9,10 @@ package net.mamoe.mirai.console.compiler.common.resolve +import net.mamoe.mirai.console.compiler.common.castOrNull +import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.constants.EnumValue /////////////////////////////////////////////////////////////////////////// // Command @@ -25,3 +28,33 @@ val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.com val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") + +/////////////////////////////////////////////////////////////////////////// +// Resolve +/////////////////////////////////////////////////////////////////////////// + +val RESOLVE_CONTEXT_FQ_NAME = FqName("net.mamoe.mirai.console.compiler.common.ResolveContext") + +/** + * net.mamoe.mirai.console.compiler.common.ResolveContext.Kind + */ +enum class ResolveContextKind { + PLUGIN_ID, + PLUGIN_NAME, + PLUGIN_VERSION, + + ; + + companion object { + fun valueOfOrNull(string: String): ResolveContextKind? = ResolveContextKind.values().find { it.name == string } + } +} + +fun Annotated.isResolveContext(kind: ResolveContextKind) = this.resolveContextKind == kind + +val Annotated.resolveContextKind: ResolveContextKind? + get() { + val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null + val (_, enumEntryName) = ann.allValueArguments.castOrNull()?.value ?: return null // undetermined kind + return ResolveContextKind.valueOf(enumEntryName.asString()) + } \ No newline at end of file diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt new file mode 100644 index 000000000..4f2e6b05c --- /dev/null +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.compiler.common.resolve + +import org.jetbrains.kotlin.descriptors.annotations.Annotated +import org.jetbrains.kotlin.name.FqName + +fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqName) +fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName) \ No newline at end of file diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt new file mode 100644 index 000000000..8bdabfe92 --- /dev/null +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.compiler.common + +import kotlin.contracts.contract + + +fun Map.firstValue(): V = this.entries.first().value +fun Map.firstKey(): K = this.entries.first().key + + +inline fun Any?.castOrNull(): T? { + contract { + returnsNotNull() implies (this@castOrNull is T) + } + return this as? T +} + +inline fun Any?.cast(): T { + contract { + returns() implies (this@cast is T) + } + return this as T +} \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index e134da7b7..b247cee62 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -21,8 +21,8 @@ kotlin.sourceSets.all { dependencies { compileOnly(kotlin("stdlib-jdk8")) - val core = "1.2.3" - val console = "1.0-M4" + val core = "1.3.0" + val console = "1.0-RC-dev-1" compileOnly("net.mamoe:mirai-console:$console") compileOnly("net.mamoe:mirai-core:$core") diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 4b0dfda16..9ee5ff208 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -1,163 +1,19 @@ -@file:Suppress("unused") - package org.example.myplugin -import com.google.auto.service.AutoService -import kotlinx.serialization.Serializable -import net.mamoe.mirai.Bot -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister -import net.mamoe.mirai.console.command.CommandSender -import net.mamoe.mirai.console.command.CompositeCommand -import net.mamoe.mirai.console.command.ConsoleCommandSender -import net.mamoe.mirai.console.command.SimpleCommand -import net.mamoe.mirai.console.data.AutoSavePluginConfig -import net.mamoe.mirai.console.data.AutoSavePluginData -import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys -import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault -import net.mamoe.mirai.console.data.value -import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission -import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin -import net.mamoe.mirai.console.util.scopeWith -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.utils.info -@AutoService(JvmPlugin::class) object MyPluginMain : KotlinPlugin( JvmPluginDescription( "org.example.example-plugin", "0.1.0" ) ) { + fun test() { - val PERMISSION_EXECUTE_1 = PermissionService.INSTANCE.register( - permissionId("execute1"), - "注册权限的示例" - ) - - - override fun onEnable() { - MySetting.reload() // 从数据库自动读取配置实例 - MyPluginData.reload() - - logger.info { "Hi: ${MySetting.name}" } // 输出一条日志. - logger.info("Hi: ${MySetting.name}") // 输出一条日志. 与上面一条相同, 但更推荐上面一条. - logger.verbose("Hi: ${MySetting.name}") // 多种日志级别可选 - - // 请不要使用 println, System.out.println 等标准输出方式. 请总是使用 logger. - - - MySetting.count++ // 对 Setting 的改动会自动在合适的时间保存 - - MySimpleCommand.register() // 注册指令 - } - - override fun onDisable() { - MySimpleCommand.unregister() // 取消注册指令 } } -// 定义插件数据 -// 插件 -object MyPluginData : AutoSavePluginData() { - var list: MutableList by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略 - var long: Long by value(0L) // 允许 var - var int by value(0) // 可以使用类型推断, 但更推荐使用 `var long: Long by value(0)` 这种定义方式. +class PM : KotlinPlugin( - - // 带默认值的非空 map. - // notnullMap[1] 的返回值总是非 null 的 MutableMap - var notnullMap - by value>>().withEmptyDefault() - - // 可将 MutableMap 映射到 MutableMap. - val botToLongMap: MutableMap by value>().mapKeys(Bot::getInstance, Bot::id) -} - -// 定义一个配置. 所有属性都会被追踪修改, 并自动保存. -// 配置是插件与用户交互的接口, 但不能用来保存插件的数据. -object MySetting : AutoSavePluginConfig() { - val name by value("test") - - var count by value(0) - - val nested by value() // 嵌套类型是支持的 -} - -@Serializable -data class MyNestedData( - val list: List = listOf(), -) - -// 简单指令 -object MySimpleCommand : SimpleCommand( - MyPluginMain, "foo", - description = "示例指令" -) { - // 会自动创建一个 ID 为 "org.example.example-plugin:command.foo" 的权限. - - - // 通过 /foo 调用, 参数自动解析 - @Handler - suspend fun CommandSender.handle(int: Int, str: String) { // 函数名随意, 但参数需要按顺序放置. - - if (this.hasPermission(MyPluginMain.PERMISSION_EXECUTE_1)) { - sendMessage("你有 ${MyPluginMain.PERMISSION_EXECUTE_1.id} 权限.") - } else { - sendMessage( - """ - 你没有 ${MyPluginMain.PERMISSION_EXECUTE_1.id} 权限. - 可以在控制台使用 /permission 管理权限. - """.trimIndent() - ) - } - - sendMessage("/foo 的第一个参数是 $int, 第二个是 $str") - } -} - -// 复合指令 -object MyCompositeCommand : CompositeCommand( - MyPluginMain, "manage", - description = "示例指令", - // prefixOptional = true // 还有更多参数可填, 此处忽略 -) { - // 会自动创建一个 ID 为 "org.example.example-plugin:command.manage" 的权限. - - // - // 在控制台执行 "/manage <群号>.<群员> <持续时间>", - // 或在聊天群内发送 "/manage <@一个群员> <持续时间>", - // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>", - // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>" - @SubCommand - suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute 调用 - sendMessage("/manage mute 被调用了, 参数为: $target, $duration") - - val result = kotlin.runCatching { - target.mute(duration).toString() - }.getOrElse { - it.stackTraceToString() - } // 失败时返回堆栈信息 - - - // 表示对 this 和 ConsoleCommandSender 一起操作 - this.scopeWith(ConsoleCommandSender) { - sendMessage("结果: $result") // 同时发送给 this@CommandSender 和 ConsoleCommandSender - } - } - - @SubCommand - suspend fun CommandSender.list() { // 执行 "/manage list" 时调用这个函数 - sendMessage("/manage list 被调用了") - } - - // 支持 Image 类型, 需在聊天中执行此指令. - @SubCommand - suspend fun CommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数 - sendMessage("/manage image 被调用了, 图片是 ${image.imageId}") - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 658a13eef..6b5841e8e 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -9,10 +9,21 @@ package net.mamoe.mirai.console.intellij.diagnostics +import com.intellij.psi.PsiElement +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.resolveStringConstantValue +import net.mamoe.mirai.console.intellij.resolve.valueParameters import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import java.util.* +import kotlin.contracts.contract /** * Checks: @@ -20,11 +31,95 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext * - plugin name */ class PluginDescriptionChecker : DeclarationChecker { + companion object { + fun checkPluginName(declaration: KtDeclaration, value: String): Diagnostic? { + return null // TODO: 2020/9/18 checkPluginName + } + + fun checkPluginId(declaration: KtDeclaration, value: String): Diagnostic? { + return null // TODO: 2020/9/18 checkPluginId + } + + fun checkPluginVersion(declaration: KtDeclaration, value: String): Diagnostic? { + return null // TODO: 2020/9/18 checkPluginVersion + } + } + + fun PsiElement.shouldPerformCheck(): Boolean { + contract { + returns(true) implies (this@shouldPerformCheck is KtCallExpression) + } + return when (this) { + is KtCallExpression, + -> true + else -> true + } + } + + private val checkersMap: EnumMap Diagnostic?> = + EnumMap Diagnostic?>(ResolveContextKind::class.java).apply { + put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) + put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) + put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion) + } + + fun check( + declaration: KtDeclaration, + expression: KtCallExpression, + 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(declaration, value)?.let { context.report(it) } + } + } + } + } + } + override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, ) { + println("${declaration::class.qualifiedName} $declaration") + when (declaration) { + is KtObjectDeclaration -> { + // check super type constructor + val superTypeCallEntry = declaration.findChildren()?.findChildren() ?: return + val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return + val valueArgumentList = superTypeCallEntry.findChildren() ?: return + valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach { + if (it.shouldPerformCheck()) { + check(declaration, it as KtCallExpression, context) + } + } + } + is KtClassOrObject -> { + // check constructor + + val superTypeCallEntry = declaration.findChildren()?.findChildren() ?: return + + val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return + val valueArgumentList = superTypeCallEntry.findChildren() ?: return + + + } + else -> { + declaration.children.filter { it.shouldPerformCheck() }.forEach { element -> + if (element is KtDeclaration) { + val desc = element.descriptor ?: return@forEach + check(element, desc, context) + } + } + } + } } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt new file mode 100644 index 000000000..6fd2874b4 --- /dev/null +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/diagnosticsUtil.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij.diagnostics + +import net.mamoe.mirai.console.intellij.resolve.getResolvedCallOrResolveToCall +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +fun DeclarationCheckerContext.report(diagnostic: Diagnostic) { + return this.trace.report(diagnostic) +} + +val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext + +fun KtElement?.getResolvedCallOrResolveToCall( + context: DeclarationCheckerContext, + bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL, +): ResolvedCall? { + return this.getResolvedCallOrResolveToCall(context.bindingContext, bodyResolveMode) +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt index 144ea41f9..43ba32e21 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/CommandDeclarationLineMarkerProvider.kt @@ -16,6 +16,7 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import net.mamoe.mirai.console.intellij.Icons +import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandlerOrCompositeCommandSubCommand import org.jetbrains.kotlin.psi.KtNamedFunction diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt index 8f5d43708..f941bea98 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt @@ -18,6 +18,9 @@ import com.intellij.psi.PsiElement import com.intellij.util.castSafelyTo import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME import net.mamoe.mirai.console.intellij.Icons +import net.mamoe.mirai.console.intellij.resolve.allSuperNames +import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark +import net.mamoe.mirai.console.intellij.resolve.parents import org.jetbrains.kotlin.nj2k.postProcessing.resolve import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtConstructor diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt deleted file mode 100644 index a407e5a6a..000000000 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/util.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.intellij.line.marker - -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.nj2k.postProcessing.resolve -import org.jetbrains.kotlin.psi.* - - -internal val KtPureClassOrObject.allSuperTypes: Sequence - get() = sequence { - yieldAll(superTypeListEntries) - for (list in superTypeListEntries.asSequence()) { - yieldAll((list.typeAsUserType?.referenceExpression?.resolve() as? KtClass)?.allSuperTypes.orEmpty()) - } - } - -internal inline fun PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() - -internal val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } - -val PsiElement.parents: Sequence - get() { - val seed = if (this is PsiFile) null else parent - return generateSequence(seed) { if (it is PsiFile) null else it.parent } - } - -internal fun getElementForLineMark(callElement: PsiElement): PsiElement = - when (callElement) { - is KtSimpleNameExpression -> callElement.getReferencedNameElement() - else -> - // a fallback, - //but who knows what to reference in KtArrayAccessExpression ? - generateSequence(callElement, { it.firstChild }).last() - } - -internal val KtAnnotationEntry.annotationClass: KtClass? - get() = calleeExpression?.constructorReferenceExpression?.resolve()?.findParent() - -internal fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = - this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index 6fba5b74b..ee7acee78 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -9,10 +9,25 @@ package net.mamoe.mirai.console.intellij.resolve +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME -import net.mamoe.mirai.console.intellij.line.marker.hasAnnotation -import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.descriptors.CallableDescriptor +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.name.FqName +import org.jetbrains.kotlin.nj2k.postProcessing.resolve +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getCall +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 /** @@ -27,3 +42,91 @@ fun KtNamedFunction.isSimpleCommandHandler(): Boolean = this.hasAnnotation(SIMPL fun KtNamedFunction.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean = this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand() + + +val KtPureClassOrObject.allSuperTypes: Sequence + get() = sequence { + yieldAll(superTypeListEntries) + for (list in superTypeListEntries.asSequence()) { + yieldAll((list.typeAsUserType?.referenceExpression?.resolve() as? KtClass)?.allSuperTypes.orEmpty()) + } + } + +fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { + val reference = typeReference + if (reference != null) { + val element = reference.typeElement + if (element is KtUserType) { + return element + } + } + return null +} + +inline fun PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() + +val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } + +val PsiElement.parents: Sequence + get() { + val seed = if (this is PsiFile) null else parent + return generateSequence(seed) { if (it is PsiFile) null else it.parent } + } + +fun getElementForLineMark(callElement: PsiElement): PsiElement = + when (callElement) { + is KtSimpleNameExpression -> callElement.getReferencedNameElement() + else -> + // a fallback, + //but who knows what to reference in KtArrayAccessExpression ? + generateSequence(callElement, { it.firstChild }).last() + } + +val KtAnnotationEntry.annotationClass: KtClass? + get() = calleeExpression?.constructorReferenceExpression?.resolve()?.findParent() + +fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = + this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } + +val PsiElement.allChildrenFlat: Sequence + get() { + return sequence { + for (child in children) { + yield(child) + yieldAll(child.allChildrenFlat) + } + } + } + +inline fun PsiElement.findChildren(): E? = this.children.find { it is E } as E? + +fun KtElement?.getResolvedCallOrResolveToCall( + context: BindingContext, + bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL, +): ResolvedCall? { + return this?.getCall(context)?.getResolvedCall(context) ?: this?.resolveToCall(bodyResolveMode) +} + +val ResolvedCall.valueParameters: List get() = this.resultingDescriptor.valueParameters + +fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): String? { + when (this) { + is KtStringTemplateExpression -> { + if (hasInterpolation()) return null + return entries.joinToString("") { it.text } + } + is KtCallExpression -> { + val callee = this.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext)?.resultingDescriptor + if (callee is VariableDescriptor) { + val compileTimeConstant = callee.compileTimeInitializer ?: return null + return compileTimeConstant.castOrNull()?.value + } + return null + } + is KtConstantExpression -> { + // TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression + } + else -> return null + } + return null +} \ No newline at end of file From 5dbf596582e5eb3d478bc48570bb395945d2ed85 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 01:49:08 +0800 Subject: [PATCH 018/114] Support checking plugin name and plugin id (ILLEGAL_PLUGIN_DESCRIPTION) --- .../console/compiler/common/ResolveContext.kt | 8 ++-- .../plugin/description/PluginDescription.kt | 5 +++ buildSrc/src/main/kotlin/Versions.kt | 2 +- .../diagnostics/MiraiConsoleErrors.java | 3 +- .../MiraiConsoleErrorsRendering.kt | 12 ++---- .../common/resolve/MiraiConsoleTypes.kt | 3 +- .../projects/test-project/build.gradle.kts | 2 +- .../org/example/myplugin/MyPluginMain.kt | 15 +++---- .../diagnostics/PluginDescriptionChecker.kt | 43 ++++++++++++++----- 9 files changed, 57 insertions(+), 36 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index b020dbd7c..fc41edc14 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -17,11 +17,13 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断. */ @ConsoleExperimentalApi -@Target(AnnotationTarget.VALUE_PARAMETER, +@Target( + AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, - AnnotationTarget.EXPRESSION) -@Retention(AnnotationRetention.SOURCE) + //AnnotationTarget.EXPRESSION +) +@Retention(AnnotationRetention.BINARY) public annotation class ResolveContext( val kind: Kind, ) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt index e9af5a3b1..85c199628 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.console.plugin.description 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 @@ -46,6 +48,7 @@ public interface PluginDescription { * @see ID_REGEX * @see FORBIDDEN_ID_NAMES */ + @ResolveContext(PLUGIN_ID) public val id: String /** @@ -60,6 +63,7 @@ public interface PluginDescription { * * @see FORBIDDEN_ID_NAMES */ + @ResolveContext(PLUGIN_NAME) public val name: String /** @@ -88,6 +92,7 @@ public interface PluginDescription { * * @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本. */ + @ResolveContext(PLUGIN_VERSION) public val version: Semver /** diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index e61e28505..3349612e6 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { 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 consoleTerminal = "0.1.0" const val consolePure = console diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index 2b65a3bd8..b8e6b9be8 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -16,8 +16,7 @@ import org.jetbrains.kotlin.diagnostics.Errors; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; public interface MiraiConsoleErrors { - DiagnosticFactory1 ILLEGAL_PLUGIN_ID = DiagnosticFactory1.create(ERROR); - DiagnosticFactory1 ILLEGAL_PLUGIN_NAME = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index 8eb25bc37..a8af96814 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -9,8 +9,7 @@ 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_NAME +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.Renderers @@ -18,13 +17,8 @@ import org.jetbrains.kotlin.diagnostics.rendering.Renderers object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply { put( - ILLEGAL_PLUGIN_ID, - "Illegal plugin id: '{0}'", - Renderers.STRING - ) - put( - ILLEGAL_PLUGIN_NAME, - "Illegal plugin name: '{0}'", + ILLEGAL_PLUGIN_DESCRIPTION, + "{0}", Renderers.STRING ) } diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt index 48ba53148..fa59374bd 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.console.compiler.common.resolve 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.name.FqName import org.jetbrains.kotlin.resolve.constants.EnumValue @@ -55,6 +56,6 @@ fun Annotated.isResolveContext(kind: ResolveContextKind) = this.resolveContextKi val Annotated.resolveContextKind: ResolveContextKind? get() { val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null - val (_, enumEntryName) = ann.allValueArguments.castOrNull()?.value ?: return null // undetermined kind + val (_, enumEntryName) = ann.allValueArguments.firstValue().castOrNull()?.value ?: return null // undetermined kind return ResolveContextKind.valueOf(enumEntryName.asString()) } \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index b247cee62..d26b54d1d 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { compileOnly(kotlin("stdlib-jdk8")) 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-core:$core") diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 9ee5ff208..7abb6a55c 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -5,15 +5,14 @@ import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin object MyPluginMain : KotlinPlugin( JvmPluginDescription( - "org.example.example-plugin", - "0.1.0" - ) + "net.mamoe.main", + "0.1.0", + ) { + name(".") + id("") + } ) { fun test() { } -} - -class PM : KotlinPlugin( - -) \ No newline at end of file +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 6b5841e8e..5f8c0b64a 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.console.intellij.diagnostics 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 @@ -32,15 +33,36 @@ import kotlin.contracts.contract */ class PluginDescriptionChecker : DeclarationChecker { companion object { - fun checkPluginName(declaration: KtDeclaration, value: String): Diagnostic? { - return null // TODO: 2020/9/18 checkPluginName + 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 = 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? { - return null // TODO: 2020/9/18 checkPluginId + fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? { + 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 } } @@ -56,15 +78,14 @@ class PluginDescriptionChecker : DeclarationChecker { } } - private val checkersMap: EnumMap Diagnostic?> = - EnumMap Diagnostic?>(ResolveContextKind::class.java).apply { + private val checkersMap: EnumMap Diagnostic?> = + EnumMap Diagnostic?>(ResolveContextKind::class.java).apply { put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion) } fun check( - declaration: KtDeclaration, expression: KtCallExpression, context: DeclarationCheckerContext, ) { @@ -76,7 +97,7 @@ class PluginDescriptionChecker : DeclarationChecker { val value = argument.getArgumentExpression() ?.resolveStringConstantValue(context.bindingContext) ?: continue 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 -> { // check super type constructor val superTypeCallEntry = declaration.findChildren()?.findChildren() ?: return - val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return + // val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return val valueArgumentList = superTypeCallEntry.findChildren() ?: return valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach { if (it.shouldPerformCheck()) { - check(declaration, it as KtCallExpression, context) + check(it as KtCallExpression, context) } } From 961bbfba53abbbffc235383ed2ae5d8c8e7b7a99 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 01:50:41 +0800 Subject: [PATCH 019/114] Make dsl markers BINARY to be resolved --- .../console/internal/MiraiConsoleImplementationBridge.kt | 4 ++-- .../mirai/console/plugin/description/VersionRequirement.kt | 4 ++-- .../mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index a971e6e3a..09d354e12 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -221,9 +221,9 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI } @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.SOURCE) + @Retention(AnnotationRetention.BINARY) @DslMarker - private annotation class ILoveOmaeKumikoForever + internal annotation class ILoveOmaeKumikoForever @ILoveOmaeKumikoForever private inline fun phase(block: () -> Unit) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt index b8ec88690..e3a3e6680 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt @@ -235,8 +235,8 @@ public sealed class VersionRequirement { } @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.SOURCE) + @Retention(AnnotationRetention.BINARY) @DslMarker - private annotation class ILoveKafuuChinoForever + internal annotation class ILoveKafuuChinoForever } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index a4e865b81..0b61101bc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -226,7 +226,7 @@ constructor( * 标注一个 [JvmPluginDescription] DSL */ @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.SOURCE) + @Retention(AnnotationRetention.BINARY) @DslMarker internal annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5 } From 6a2ef97b41a46a2c15ebc21146eddd6c089a74fc Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 10:07:50 +0800 Subject: [PATCH 020/114] Improve description checker: support compile-time constants --- .../.images/ILLEGAL_PLUGIN_DESCRIPTION.png | Bin 0 -> 15581 bytes tools/intellij-plugin/README.md | 12 ++++- .../org/example/myplugin/MyPluginMain.kt | 18 +++++++- .../diagnostics/PluginDescriptionChecker.kt | 42 +++++++++--------- .../console/intellij/resolve/resolveIdea.kt | 21 ++++++++- 5 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 tools/intellij-plugin/.images/ILLEGAL_PLUGIN_DESCRIPTION.png diff --git a/tools/intellij-plugin/.images/ILLEGAL_PLUGIN_DESCRIPTION.png b/tools/intellij-plugin/.images/ILLEGAL_PLUGIN_DESCRIPTION.png new file mode 100644 index 0000000000000000000000000000000000000000..830a907e4df699d88437723a70266df4ec9c698d GIT binary patch literal 15581 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 literal 0 HcmV?d00001 diff --git a/tools/intellij-plugin/README.md b/tools/intellij-plugin/README.md index da5b520e0..1d79320c2 100644 --- a/tools/intellij-plugin/README.md +++ b/tools/intellij-plugin/README.md @@ -4,4 +4,14 @@ IntelliJ 平台的 Mirai Console 开发插件 ## 功能 -### 诊断 \ No newline at end of file +### 诊断 + +#### 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) diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 7abb6a55c..9f1bdcbd5 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -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(".") diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 5f8c0b64a..74f68de7b 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -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 = 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()?.findChildren() ?: return + val superTypeCallEntry = declaration.findChild()?.findChild() ?: return // val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return - val valueArgumentList = superTypeCallEntry.findChildren() ?: return + val valueArgumentList = superTypeCallEntry.findChild() ?: 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()?.findChildren() ?: return + val superTypeCallEntry = declaration.findChild()?.findChild() ?: return - val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return - val valueArgumentList = superTypeCallEntry.findChildren() ?: return + val constructorCall = superTypeCallEntry.findChild()?.resolveToCall() ?: return + val valueArgumentList = superTypeCallEntry.findChild() ?: return } diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index ee7acee78..b0c261942 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -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 } } -inline fun PsiElement.findChildren(): E? = this.children.find { it is E } as E? +inline fun PsiElement.findChild(): E? = this.children.find { it is E } as E? fun KtElement?.getResolvedCallOrResolveToCall( context: BindingContext, @@ -111,10 +115,23 @@ val ResolvedCall.valueParameters: List { + when (val reference = references.firstIsInstance().resolve()) { + is KtDeclaration -> { + val descriptor = reference.descriptor.castOrNull() ?: return null + val compileTimeConstant = descriptor.compileTimeInitializer ?: return null + return compileTimeConstant.castOrNull()?.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()?.value } return null - } + }*/ is KtConstantExpression -> { // TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression } From 9da8ce882e73c14ed85b3a5f24888dafd19005e8 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 10:37:36 +0800 Subject: [PATCH 021/114] Check into lambda expressions for inspections --- .../diagnostics/PluginDescriptionChecker.kt | 46 ++++++++----------- .../console/intellij/resolve/resolveIdea.kt | 11 ++++- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 74f68de7b..22b308328 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -13,18 +13,17 @@ 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.allChildrenWithSelf 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 import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.util.* -import kotlin.contracts.contract /** * Checks: @@ -69,17 +68,6 @@ class PluginDescriptionChecker : DeclarationChecker { } } - fun PsiElement.shouldPerformCheck(): Boolean { - contract { - returns(true) implies (this@shouldPerformCheck is KtCallExpression) - } - return when (this) { - is KtCallExpression, - -> true - else -> true - } - } - private val checkersMap: EnumMap Diagnostic?> = EnumMap Diagnostic?>(ResolveContextKind::class.java).apply { put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) @@ -111,30 +99,32 @@ class PluginDescriptionChecker : DeclarationChecker { ) { println("${declaration::class.qualifiedName} $declaration") when (declaration) { - is KtObjectDeclaration -> { + is KtClassOrObject -> { // check super type constructor val superTypeCallEntry = declaration.findChild()?.findChild() ?: return // val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return val valueArgumentList = superTypeCallEntry.findChild() ?: return valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach { - if (it.shouldPerformCheck()) { - check(it as KtCallExpression, context) + for (child in it.allChildrenWithSelf) { + if (child is LambdaArgument) { + child.getLambdaExpression()?.bodyExpression?.statements?.forEach { statement -> + if (statement is KtCallExpression) check(statement, context) + } + } + if (child is KtCallExpression) { + check(child, context) + } } } - - } - is KtClassOrObject -> { - // check constructor - - val superTypeCallEntry = declaration.findChild()?.findChild() ?: return - - val constructorCall = superTypeCallEntry.findChild()?.resolveToCall() ?: return - val valueArgumentList = superTypeCallEntry.findChild() ?: return - - } else -> { - declaration.children.filter { it.shouldPerformCheck() }.forEach { element -> + declaration.children.flatMap { + when (it) { + is KtCallExpression -> listOf(it) + is KtLambdaExpression -> it.bodyExpression?.statements.orEmpty() + else -> emptyList() + } + }.forEach { element -> if (element is KtDeclaration) { val desc = element.descriptor ?: return@forEach check(element, desc, context) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index b0c261942..caabf29ef 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -25,6 +25,7 @@ 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.* +import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -92,6 +93,14 @@ val KtAnnotationEntry.annotationClass: KtClass? fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } +val PsiElement.allChildrenWithSelf: Sequence + get() { + return sequence { + yield(this@allChildrenWithSelf) + yieldAll(allChildren) + } + } + val PsiElement.allChildrenFlat: Sequence get() { return sequence { @@ -123,7 +132,7 @@ fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): Str return compileTimeConstant.castOrNull()?.value } is PsiDeclarationStatement -> { - + // TODO: 2020/9/18 compile-time constants from Java } } } From 0c1bf9ce9bd4397063be9ee9d95a108837dacb4e Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 10:47:52 +0800 Subject: [PATCH 022/114] Support checking plugin version --- .../intellij/diagnostics/PluginDescriptionChecker.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 22b308328..81e7b6b94 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -37,6 +37,12 @@ class PluginDescriptionChecker : DeclarationChecker { private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名. """ + /** + * https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + */ + 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? { 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, @@ -64,7 +70,10 @@ class PluginDescriptionChecker : DeclarationChecker { } fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? { - return null // TODO: 2020/9/18 checkPluginVersion + if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { + return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") + } + return null } } From e299ecffb91883dceed71a86363acd19a3cacc65 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 11:11:31 +0800 Subject: [PATCH 023/114] Improve performance --- .../diagnostics/PluginDescriptionChecker.kt | 47 +++++++++---------- .../console/intellij/resolve/resolveIdea.kt | 23 +++++++-- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 81e7b6b94..8b6c75501 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -13,14 +13,12 @@ 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.allChildrenWithSelf -import net.mamoe.mirai.console.intellij.resolve.findChild +import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValue -import net.mamoe.mirai.console.intellij.resolve.valueParameters +import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.util.* @@ -84,29 +82,30 @@ class PluginDescriptionChecker : DeclarationChecker { put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion) } - fun check( - expression: KtCallExpression, - context: DeclarationCheckerContext, - ) { - val call = expression.calleeExpression.getResolvedCallOrResolveToCall(context) ?: return // unresolved - 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) } - } - } - } - } - override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, ) { - println("${declaration::class.qualifiedName} $declaration") + declaration.resolveAllCalls(context.bindingContext) + .flatMap { call -> + call.valueParametersWithArguments().asSequence() + } + .mapNotNull { (p, a) -> + p.resolveContextKind?.takeIf { it in checkersMap }?.let { it to a } + } + .mapNotNull { (kind, argument) -> + argument.resolveStringConstantValue(context.bindingContext)?.let { const -> + Triple(kind, argument, const) + } + } + .forEach { (parameterContextKind, argument, resolvedConstant) -> + for ((kind, fn) in checkersMap) { + if (parameterContextKind == kind) fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } + } + } + return + /* when (declaration) { is KtClassOrObject -> { // check super type constructor @@ -140,6 +139,6 @@ class PluginDescriptionChecker : DeclarationChecker { } } } - } + }*/ } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index caabf29ef..d2e240cd4 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -25,7 +25,6 @@ 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.* -import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -94,13 +93,27 @@ fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } val PsiElement.allChildrenWithSelf: Sequence - get() { - return sequence { - yield(this@allChildrenWithSelf) - yieldAll(allChildren) + get() = sequence { + yield(this@allChildrenWithSelf) + for (child in children) { + yieldAll(child.allChildrenWithSelf) } } +fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence> { + return allChildrenWithSelf + .filterIsInstance() + .mapNotNull { it.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext) } +} + +fun ResolvedCall<*>.valueParametersWithArguments(): List> { + return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty()) +} + +fun ValueArgument.resolveStringConstantValue(bindingContext: BindingContext): String? { + return this.getArgumentExpression()?.resolveStringConstantValue(bindingContext) +} + val PsiElement.allChildrenFlat: Sequence get() { return sequence { From e4f37b9a5221e0b75656fe5ec2842255b8794697 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 12:09:49 +0800 Subject: [PATCH 024/114] Improve performance --- .../intellij/diagnostics/PluginDescriptionChecker.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt index 8b6c75501..b3f0b3d1c 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt @@ -92,17 +92,15 @@ class PluginDescriptionChecker : DeclarationChecker { call.valueParametersWithArguments().asSequence() } .mapNotNull { (p, a) -> - p.resolveContextKind?.takeIf { it in checkersMap }?.let { it to a } + p.resolveContextKind?.let(checkersMap::get)?.let { it to a } } .mapNotNull { (kind, argument) -> argument.resolveStringConstantValue(context.bindingContext)?.let { const -> Triple(kind, argument, const) } } - .forEach { (parameterContextKind, argument, resolvedConstant) -> - for ((kind, fn) in checkersMap) { - if (parameterContextKind == kind) fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } - } + .forEach { (fn, argument, resolvedConstant) -> + fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } } return /* From 001fac65cc08a4da322c1847910964106e970955 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 18 Sep 2020 12:34:30 +0800 Subject: [PATCH 025/114] Update SemVersion rules --- .../internal/util/SemVersionInternal.kt | 4 +- .../mamoe/mirai/console/util/SemVersion.kt | 45 ++++---- .../mirai/console/util/TestSemVersion.kt | 100 ++++++++++++++++-- 3 files changed, 115 insertions(+), 34 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index e09fd87cc..f880a8ecd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -184,8 +184,8 @@ internal object SemVersionInternal { identifier0 = identifier0.substring(ignoredSize) identifier1 = identifier1.substring(ignoredSize) // Multi-chunk comparing - val chunks0 = identifier0.split('-', '.', '_') - val chunks1 = identifier1.split('-', '.', '_') + val chunks0 = identifier0.split('-', '.') + val chunks1 = identifier1.split('-', '.') chunkLoop@ for (index in 0 until (max(chunks0.size, chunks1.size))) { val value0 = chunks0.getOrNull(index) val value1 = chunks1.getOrNull(index) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 02a6f9587..9063f226f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -39,16 +39,14 @@ import net.mamoe.mirai.console.util.SemVersion.Companion.equals * metadata = "c25733b8" * ) * ``` - * 其中 identifier 和 metadata 都是可选的, 该实现对于 mainVersion 的最大长度不作出限制, - * 也建议 mainVersion 的长度不要过长或过短 - * 但是必须至少拥有两位及以上的版本描述符, (即必须拥有主版本号和次版本号). - * - * 比如 `1-M4` 是不合法的, 但是 `1.0-M4` 是合法的 + * 其中 identifier 和 metadata 都是可选的. + * 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在. + * 但是不允许 0.0.0.0 之类的存在 * */ @Serializable public data class SemVersion internal constructor( - /** 核心版本号, 至少包含一个主版本号和一个次版本号 */ + /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */ public val mainVersion: IntArray, /** 先行版本号识别符 */ public val identifier: String? = null, @@ -65,6 +63,9 @@ public data class SemVersion internal constructor( } public companion object { + 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() + /** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */ @JvmStatic private fun String.parseMainVersion(): IntArray = @@ -78,14 +79,23 @@ public data class SemVersion internal constructor( * - 必须包含主版本号和次版本号 * - 存在 先行版本号 的时候 先行版本号 不能为空 * - 存在 元数据 的时候 元数据 不能为空 + * - 核心版本号只允许 `x.y` 和 `x.y.z` 的存在 + * - `1.0-RC` 是合法的 + * - `1.0.0-RC` 也是合法的, 与 `1.0-RC` 一样 + * - `1.0.0.0-RC` 是不合法的, 将会抛出一个 [IllegalArgumentException] * * 注意情况: * - 第一个 `+` 之后的所有内容全部识别为元数据 * - `1.0+METADATA-M4`, metadata="METADATA-M4" + * - 如果不确定版本号是否合法, 可以使用 [regex101.com](https://regex101.com/r/vkijKf/1/) 进行检查 + * - 此实现使用的正则表达式为 `^(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-]+)*))?$` */ @Throws(IllegalArgumentException::class, NumberFormatException::class) @JvmStatic public fun parse(version: String): SemVersion { + if (!SEM_VERSION_REGEX.matches(version)) { + throw IllegalArgumentException("`$version` not a valid version") + } var mainVersionEnd: Int = 0 kotlin.run { val iterator = version.iterator() @@ -116,24 +126,9 @@ public data class SemVersion internal constructor( } } return SemVersion( - mainVersion = version.substring(0, mainVersionEnd).also { mainVersion -> - if (mainVersion.indexOf('.') == -1) { - throw IllegalArgumentException("$mainVersion must has more than one label") - } - if (mainVersion.last() == '.') { - throw IllegalArgumentException("Version string cannot end-with `.`") - } - }.parseMainVersion(), - identifier = identifier?.also { - if (it.isBlank()) { - throw IllegalArgumentException("The identifier cannot be blank.") - } - }, - metadata = metadata?.also { - if (it.isBlank()) { - throw IllegalArgumentException("The metadata cannot be blank.") - } - } + mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(), + identifier = identifier, + metadata = metadata ) } @@ -153,7 +148,7 @@ public data class SemVersion internal constructor( * * 对于多个规则, 也允许使用 `||` 拼接在一起. * 例如: - * - `1.x || 2.x || 3.0` + * - `1.x || 2.x || 3.0.0` * - `<= 0.5.3 || >= 1.0.0` * * 特别注意: diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index 36923773b..28d7b234d 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -23,7 +23,7 @@ internal class TestSemVersion { fun String.sem(): SemVersion = SemVersion.parse(this) assert("1.0".sem() < "1.0.1".sem()) assert("1.0.0".sem() == "1.0".sem()) - assert("1.1".sem() > "1.0.0.1".sem()) + assert("1.1".sem() > "1.0.0".sem()) assert("1.0-M4".sem() < "1.0-M5".sem()) assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem()) assert("1.0-M5-dev-79".sem() < "1.0-M5-dev-7001".sem()) @@ -54,7 +54,6 @@ internal class TestSemVersion { } SemVersion.parseRangeRequirement("1.0") .assert("1.0").assert("1.0.0") - .assert("1.0.0.0") .assertFalse("1.1.0").assertFalse("2.0.0") SemVersion.parseRangeRequirement("1.x") .assert("1.0").assert("1.1") @@ -62,12 +61,12 @@ internal class TestSemVersion { .assertFalse("2.33") SemVersion.parseRangeRequirement("2.0 || 1.2.x") .assert("2.0").assert("2.0.0") - .assertFalse("2.1").assertFalse("2.0.0.1") + .assertFalse("2.1") .assert("1.2.5").assert("1.2.0").assertFalse("1.2") .assertFalse("1.0.0") - SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919.810") + SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919") .assert("1.0.0") - .assert("114.514").assert("114.514.1919.810") + .assert("114.514").assert("114.514.1919") .assertFalse("0.0.1") .assertFalse("4444.4444") SemVersion.parseRangeRequirement("[1.0.0, 19190.0]") @@ -75,7 +74,7 @@ internal class TestSemVersion { .assert("19190.0").assertFalse("19198.10") SemVersion.parseRangeRequirement(" >= 1.0.0") .assert("1.0.0") - .assert("114.514.1919.810") + .assert("114.514.1919") .assertFalse("0.0.0") .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") @@ -85,6 +84,16 @@ internal class TestSemVersion { } + private fun String.check() { + val sem = SemVersion.parse(this) + assert(this == sem.toString()) { "$this != $sem" } + } + + private fun String.checkInvalid() { + kotlin.runCatching { SemVersion.parse(this) } + .onSuccess { assert(false) { "$this not a invalid sem-version" } } + } + @Test internal fun testSemVersionParsing() { fun String.check() { @@ -99,7 +108,7 @@ internal class TestSemVersion { } "0.0".check() "1.0.0".check() - "1.2.3.4.5.6.7.8".check() + "1.2.3.4.5.6.7.8".checkInvalid() "5555.0-A".check() "5555.0-A+METADATA".check() "5555.0+METADATA".check() @@ -113,4 +122,81 @@ internal class TestSemVersion { "5.1+68-7".check() "5.1+68-".check() } + @Test + internal fun testSemVersionOfficial(){ + """ + 1.0-RC + 0.0.4 + 1.2.3 + 10.20.30 + 1.1.2-prerelease+meta + 1.1.2+meta + 1.1.2+meta-valid + 1.0.0-alpha + 1.0.0-beta + 1.0.0-alpha.beta + 1.0.0-alpha.beta.1 + 1.0.0-alpha.1 + 1.0.0-alpha0.valid + 1.0.0-alpha.0valid + 1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay + 1.0.0-rc.1+build.1 + 2.0.0-rc.1+build.123 + 1.2.3-beta + 10.2.3-DEV-SNAPSHOT + 1.2.3-SNAPSHOT-123 + 1.0.0 + 2.0.0 + 1.1.7 + 2.0.0+build.1848 + 2.0.1-alpha.1227 + 1.0.0-alpha+beta + 1.2.3----RC-SNAPSHOT.12.9.1--.12+788 + 1.2.3----R-S.12.9.1--.12+meta + 1.2.3----RC-SNAPSHOT.12.9.1--.12 + 1.0.0+0.build.1-rc.10000aaa-kk-0.1 + 1.0.0-0A.is.legal + """.trimIndent().split('\n').asSequence() + .filter { it.isNotBlank() }.map { it.trim() }.forEach { it.check() } + """ + 1 + 1.2.3-0123 + 1.2.3-0123.0123 + 1.1.2+.123 + +invalid + -invalid + -invalid+invalid + -invalid.01 + alpha + alpha.beta + alpha.beta.1 + alpha.1 + alpha+beta + alpha_beta + alpha. + alpha.. + beta + 1.0.0-alpha_beta + -alpha. + 1.0.0-alpha.. + 1.0.0-alpha..1 + 1.0.0-alpha...1 + 1.0.0-alpha....1 + 1.0.0-alpha.....1 + 1.0.0-alpha......1 + 1.0.0-alpha.......1 + 01.1.1 + 1.01.1 + 1.1.01 + 1.2.3.DEV + 1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 + 1.2.31.2.3-RC + -1.0.3-gamma+b7718 + +justmeta + 9.8.7+meta+meta + 9.8.7-whatever+meta+meta + 99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12 + """.trimIndent().split('\n').asSequence() + .filter { it.isNotBlank() }.map { it.trim() }.forEach { it.checkInvalid() } + } } \ No newline at end of file From a49efda337cbf885183715887e82eb286c23392f Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 18 Sep 2020 12:37:40 +0800 Subject: [PATCH 026/114] Add serializer for SemVersion --- .../kotlin/net/mamoe/mirai/console/util/SemVersion.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 9063f226f..783becc0c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -14,8 +14,11 @@ package net.mamoe.mirai.console.util +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals @@ -44,7 +47,7 @@ import net.mamoe.mirai.console.util.SemVersion.Companion.equals * 但是不允许 0.0.0.0 之类的存在 * */ -@Serializable +@Serializable(with = SemVersion.SemVersionAsStringSerializer::class) public data class SemVersion internal constructor( /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */ public val mainVersion: IntArray, @@ -62,6 +65,11 @@ public data class SemVersion internal constructor( public fun check(version: SemVersion): Boolean } + public object SemVersionAsStringSerializer : KSerializer by String.serializer().map( + serializer = { it.toString() }, + deserializer = { parse(it) } + ) + public companion object { 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() From f990b7ce8cf8279dd62dee8cf296412582d552e4 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 18 Sep 2020 12:40:18 +0800 Subject: [PATCH 027/114] Remove redundant code --- .../net/mamoe/mirai/console/util/TestSemVersion.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index 28d7b234d..56d34ab71 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -96,16 +96,6 @@ internal class TestSemVersion { @Test internal fun testSemVersionParsing() { - fun String.check() { - val sem = SemVersion.parse(this) - assert(this == sem.toString()) { "$this != $sem" } - } - - fun String.checkInvalid() { - kotlin.runCatching { SemVersion.parse(this) } - .onSuccess { assert(false) { "$this not a invalid sem-version" } } - .onFailure { println("$this - $it") } - } "0.0".check() "1.0.0".check() "1.2.3.4.5.6.7.8".checkInvalid() From 8fe2506e75c886774bef65f250ee00c270b41929 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 18 Sep 2020 12:43:57 +0800 Subject: [PATCH 028/114] Terminal (#179) * Rename ConsolePure to ConsoleTerminal * Fix the way to close console with Ctrl+C * Fix windows pipeline error. Fix EndOfFileException * Add ConsoleExperimentalApi * Collect imports * Review - Change old CLI main deprecation level to ERROR - Update documents - Update tasks from pure to terminal * Fix error in closing console. * Fix terminal closing and Ctrl+C closing. * Add console shut-downing status * Don't invokeOnCompletion when console shut-downing * Fix Input interrupt * Ensure active unless console is shut downing * Change MiraiConsole.isShutDowning to `!job.isActive` * Code Review - Update Message on MiraiConsolePureLoader.kt - Change MiraiConsole.isShutDowning to MiraiConsole.isActive - Change MiraiConsole.shutdown to ConsoleInternalApi * run catching * Fix console input * Update shutdown * Fix module * Revert 5199395 * Typo --- .github/workflows/bintray.yml | 4 +- .github/workflows/shadow.yml | 4 +- README.md | 7 +- .../net/mamoe/mirai/console/MiraiConsole.kt | 5 ++ .../plugin/BuiltInJvmPluginLoaderImpl.kt | 4 +- .../internal/plugin/JvmPluginInternal.kt | 1 + buildSrc/src/main/kotlin/Versions.kt | 3 +- docs/Run.md | 16 ++-- .../build.gradle.kts | 6 +- .../console/pure/MiraiConsolePureLoader.kt | 34 ++++++++ .../console/terminal}/BufferedOutputStream.kt | 3 +- .../console/terminal/ConsoleInputImpl.kt | 74 ++++++++++++++++ .../terminal/ConsoleTerminalSettings.kt} | 9 +- .../mirai/console/terminal}/ConsoleThread.kt | 37 +++++--- .../MiraiConsoleImplementationTerminal.kt} | 86 ++++++++++--------- .../terminal/MiraiConsoleTerminalLoader.kt} | 49 ++++++----- .../console/terminal}/noconsole/NoConsole.kt | 22 ++--- settings.gradle.kts | 2 +- .../src/test/kotlin/RunConsole.kt | 4 +- 19 files changed, 256 insertions(+), 114 deletions(-) rename frontend/{mirai-console-pure => mirai-console-terminal}/build.gradle.kts (93%) create mode 100644 frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt rename frontend/{mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure => mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal}/BufferedOutputStream.kt (98%) create mode 100644 frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleInputImpl.kt rename frontend/{mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt => mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleTerminalSettings.kt} (87%) rename frontend/{mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure => mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal}/ConsoleThread.kt (76%) rename frontend/{mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt => mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt} (69%) rename frontend/{mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt => mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt} (78%) rename frontend/{mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure => mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal}/noconsole/NoConsole.kt (90%) diff --git a/.github/workflows/bintray.yml b/.github/workflows/bintray.yml index c4abe1dba..425caf74b 100644 --- a/.github/workflows/bintray.yml +++ b/.github/workflows/bintray.yml @@ -32,6 +32,6 @@ jobs: run: ./gradlew :mirai-console:fillBuildConstants -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-console:bintrayUpload run: ./gradlew :mirai-console:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - - name: Gradle :mirai-console-pure:bintrayUpload - run: ./gradlew :mirai-console-pure:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-terminal:bintrayUpload + run: ./gradlew :mirai-console-terminal:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml index da3bccf38..4b52408e7 100644 --- a/.github/workflows/shadow.yml +++ b/.github/workflows/shadow.yml @@ -28,8 +28,8 @@ jobs: run: ./gradlew build # if test's failed, don't publish - name: Gradle :mirai-console:githubUpload run: ./gradlew :mirai-console:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }} - - name: Gradle :mirai-console-pure:githubUpload - run: ./gradlew :mirai-console-pure:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }} + - name: Gradle :mirai-console-terminal:githubUpload + run: ./gradlew :mirai-console-terminal:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }} # - name: Upload artifact diff --git a/README.md b/README.md index 84f369e09..a52353cb1 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,11 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. 前端: -- `mirai-console-pure`: console 的轻量命令行前端. +- `mirai-console-terminal`: console 的 Unix 终端界面前端. - `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. (开发中) -- `mirai-console-terminal`: console 的 Unix 终端界面前端. (开发中) -**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构, 所有 API 都不具有稳定性** +**注意:`mirai-console` 后端和 terminal 前端正在进行完全的重构, 所有 API 都不具有稳定性** ### 使用 @@ -56,7 +55,7 @@ dependencies { implementation("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API implementation("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端 - testImplementation("net.mamoe:mirai-console-pure:$CONSOLE_VERSION") // 前端, 用于启动测试 + testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试 } ``` diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 8bed464da..15f4b9a01 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start +import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.extensions.BotConfigurationAlterer import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage @@ -146,6 +147,10 @@ public interface MiraiConsole : CoroutineScope { else -> null!! } } + + @ConsoleExperimentalApi("This is a low-level API and might be removed in the future.") + public val isActive: Boolean + get() = job.isActive } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt index 63397ed3e..ad335f874 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt @@ -106,7 +106,9 @@ internal object BuiltInJvmPluginLoaderImpl : override fun disable(plugin: JvmPlugin) { if (!plugin.isEnabled) return - ensureActive() + + if (MiraiConsole.isActive) + ensureActive() if (plugin is JvmPluginInternal) { plugin.internalOnDisable() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index 8dda88fa7..9cd06244f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -151,6 +151,7 @@ internal abstract class JvmPluginInternal( ) ) .also { + if (!MiraiConsole.isActive) return@also BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion { this.cancel() } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 3349612e6..70d9ba45e 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,8 +11,7 @@ object Versions { const val core = "1.3.0" const val console = "1.0-RC-dev-2" const val consoleGraphical = "0.0.7" - const val consoleTerminal = "0.1.0" - const val consolePure = console + const val consoleTerminal = console const val kotlinCompiler = "1.4.10" const val kotlinStdlib = kotlinCompiler diff --git a/docs/Run.md b/docs/Run.md index 81a25f4ec..91a8c0fb5 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -19,16 +19,16 @@ https://github.com/LXY1226/MiraiOK - mirai-console 任一前端 - 相关依赖 -只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 pure 前端可用。 +只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。 -### 启动 mirai-console-pure 前端 +### 启动 mirai-console-terminal 前端 mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。 1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`): - mirai-core-qqandroid - mirai-console - - mirai-console-pure + - mirai-console-terminal 2. 创建一个新的文件, 名为 `start-mirai-console.bat`/`start-mirai-console.ps1`/`start-mirai-console.sh` @@ -36,26 +36,26 @@ Windows CMD: ```shell script @echo off title Mirai Console -java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader %* +java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader %* pause ``` Windows PowerShell: ```shell script $Host.UI.RawUI.WindowTitle = "Mirai Console" -java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $args +java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $args pause ``` Linux: ```shell script #!/usr/bin/env bash -java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $* +java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $* ``` 然后就可以开始使用 mirai-console 了 -### mirai-console-pure 前端参数 -使用 `./start-mirai-console --help` 查看 mirai-console-pure 支持的启动参数 +### mirai-console-terminal 前端参数 +使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数 [mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow diff --git a/frontend/mirai-console-pure/build.gradle.kts b/frontend/mirai-console-terminal/build.gradle.kts similarity index 93% rename from frontend/mirai-console-pure/build.gradle.kts rename to frontend/mirai-console-terminal/build.gradle.kts index 57f6dd608..2dc5414da 100644 --- a/frontend/mirai-console-pure/build.gradle.kts +++ b/frontend/mirai-console-terminal/build.gradle.kts @@ -66,10 +66,10 @@ ext.apply { this.set("shadowJar", x) } -version = Versions.consolePure +version = Versions.consoleTerminal -description = "Console Pure CLI frontend for mirai" +description = "Console Terminal CLI frontend for mirai" -setupPublishing("mirai-console-pure", bintrayPkgName = "mirai-console-pure") +setupPublishing("mirai-console-terminal", bintrayPkgName = "mirai-console-terminal") // endregion \ No newline at end of file diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt new file mode 100644 index 000000000..477d8dcdc --- /dev/null +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -0,0 +1,34 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +package net.mamoe.mirai.console.pure + +import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader + +@Deprecated( + message = "Please use MiraiConsoleTerminalLoader", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "MiraiConsoleTerminalLoader", + "net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader" + ) +) +object MiraiConsolePureLoader { + @Deprecated( + message = "for binary compatibility", + level = DeprecationLevel.ERROR + ) + @JvmStatic + fun main(args: Array) { + System.err.println("WARNING: Mirai Console Pure已经更名为 Mirai Console Terminal") + System.err.println("请使用新的入口点 net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader") + MiraiConsoleTerminalLoader.main(args) + } +} diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/BufferedOutputStream.kt similarity index 98% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt rename to frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/BufferedOutputStream.kt index f5d0375cd..ad7f18160 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/BufferedOutputStream.kt @@ -5,9 +5,10 @@ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. * * https://github.com/mamoe/mirai/blob/master/LICENSE + * */ -package net.mamoe.mirai.console.pure +package net.mamoe.mirai.console.terminal import java.io.ByteArrayOutputStream import java.io.OutputStream diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleInputImpl.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleInputImpl.kt new file mode 100644 index 000000000..c0a83f707 --- /dev/null +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleInputImpl.kt @@ -0,0 +1,74 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +package net.mamoe.mirai.console.terminal + +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine +import net.mamoe.mirai.console.util.ConsoleInput +import org.fusesource.jansi.Ansi +import org.jline.reader.EndOfFileException +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.concurrent.Executors +import kotlin.coroutines.resumeWithException + + +internal object ConsoleInputImpl : ConsoleInput { + private val format = DateTimeFormatter.ofPattern("HH:mm:ss") + internal val thread = Executors.newSingleThreadExecutor { task -> + Thread(task, "Mirai Console Input Thread").also { + it.isDaemon = false + } + } + internal var executingCoroutine: CancellableContinuation? = null + + + override suspend fun requestInput(hint: String): String { + return suspendCancellableCoroutine { coroutine -> + if (thread.isShutdown || thread.isTerminated) { + coroutine.resumeWithException(EndOfFileException()) + return@suspendCancellableCoroutine + } + executingCoroutine = coroutine + kotlin.runCatching { + thread.submit { + kotlin.runCatching { + lineReader.readLine( + if (hint.isNotEmpty()) { + lineReader.printAbove( + Ansi.ansi() + .fgCyan() + .a( + LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()) + .format(format) + ) + .a(" ") + .fgMagenta().a(hint) + .reset() + .toString() + ) + "$hint > " + } else "> " + ) + }.let { result -> + executingCoroutine = null + coroutine.resumeWith(result) + } + } + }.onFailure { error -> + executingCoroutine = null + kotlin.runCatching { coroutine.resumeWithException(EndOfFileException(error)) } + } + } + } +} diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleTerminalSettings.kt similarity index 87% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt rename to frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleTerminalSettings.kt index 5a1fdec47..0c780724e 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleTerminalSettings.kt @@ -5,12 +5,13 @@ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. * * https://github.com/mamoe/mirai/blob/master/LICENSE + * */ /* * @author Karlatemp */ -package net.mamoe.mirai.console.pure +package net.mamoe.mirai.console.terminal @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @@ -23,10 +24,10 @@ package net.mamoe.mirai.console.pure AnnotationTarget.CONSTRUCTOR ) @MustBeDocumented -annotation class ConsolePureExperimentalApi +annotation class ConsoleTerminalExperimentalApi -@ConsolePureExperimentalApi -public object ConsolePureSettings { +@ConsoleTerminalExperimentalApi +public object ConsoleTerminalSettings { @JvmField var setupAnsi: Boolean = System.getProperty("os.name") .toLowerCase() diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt similarity index 76% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt rename to frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt index 276c6ed18..a6cad4cc6 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt @@ -5,13 +5,14 @@ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. * * https://github.com/mamoe/mirai/blob/master/LICENSE + * */ -package net.mamoe.mirai.console.pure +package net.mamoe.mirai.console.terminal import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.BuiltInCommands @@ -20,18 +21,33 @@ import net.mamoe.mirai.console.command.CommandExecuteStatus import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.requestInput import net.mamoe.mirai.utils.DefaultLogger +import org.jline.reader.EndOfFileException import org.jline.reader.UserInterruptException val consoleLogger by lazy { DefaultLogger("console") } -@OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class) +@OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class) internal fun startupConsoleThread() { - if (ConsolePureSettings.noConsole) return + if (terminal is NoConsole) return - MiraiConsole.launch(CoroutineName("Input")) { + MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) { + while (true) { + delay(2000) + } + }.invokeOnCompletion { + runCatching { + terminal.close() + ConsoleInputImpl.thread.shutdownNow() + runCatching { + ConsoleInputImpl.executingCoroutine?.cancel(EndOfFileException()) + } + }.exceptionOrNull()?.printStackTrace() + } + MiraiConsole.launch(CoroutineName("Console Command")) { while (true) { try { val next = MiraiConsole.requestInput("").let { @@ -65,17 +81,14 @@ internal fun startupConsoleThread() { } catch (e: CancellationException) { return@launch } catch (e: UserInterruptException) { - MiraiConsole.cancel() + BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() } + return@launch + } catch (eof: EndOfFileException) { + consoleLogger.warning("Closing input service...") return@launch } catch (e: Throwable) { consoleLogger.error("Unhandled exception", e) } } } - - MiraiConsole.job.invokeOnCompletion { - runCatching { - terminal.close() - }.exceptionOrNull()?.printStackTrace() - } } diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt similarity index 69% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt rename to frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt index 471c02e7f..b1af02164 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt @@ -5,6 +5,7 @@ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. * * https://github.com/mamoe/mirai/blob/master/LICENSE + * */ @file:Suppress( @@ -17,13 +18,16 @@ "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING", "EXPOSED_SUPER_CLASS" ) -@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalApi::class) +@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsoleTerminalExperimentalApi::class) -package net.mamoe.mirai.console.pure +package net.mamoe.mirai.console.terminal import com.vdurmont.semver4j.Semver -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription @@ -32,10 +36,10 @@ import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader -import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput +import net.mamoe.mirai.console.terminal.ConsoleInputImpl.requestInput +import net.mamoe.mirai.console.terminal.noconsole.AllEmptyLineReader +import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleExperimentalApi -import net.mamoe.mirai.console.pure.noconsole.AllEmptyLineReader -import net.mamoe.mirai.console.pure.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.NamedSupervisorJob @@ -46,31 +50,28 @@ import org.jline.reader.LineReaderBuilder import org.jline.reader.impl.completer.NullCompleter import org.jline.terminal.Terminal import org.jline.terminal.TerminalBuilder +import org.jline.terminal.impl.AbstractWindowsTerminal import java.nio.file.Path import java.nio.file.Paths -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId -import java.time.format.DateTimeFormatter /** - * mirai-console-pure 后端实现 + * mirai-console-terminal 后端实现 * - * @see MiraiConsolePureLoader CLI 入口点 + * @see MiraiConsoleTerminalLoader CLI 入口点 */ @ConsoleExperimentalApi -class MiraiConsoleImplementationPure +class MiraiConsoleImplementationTerminal @JvmOverloads constructor( override val rootPath: Path = Paths.get(".").toAbsolutePath(), override val builtInPluginLoaders: List>> = listOf(lazy { JvmPluginLoader }), override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl, - override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplPure, + override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplTerminal, override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), ) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope( - NamedSupervisorJob("MiraiConsoleImplementationPure") + + NamedSupervisorJob("MiraiConsoleImplementationTerminal") + CoroutineExceptionHandler { coroutineContext, throwable -> if (throwable is CancellationException) { return@CoroutineExceptionHandler @@ -94,30 +95,9 @@ class MiraiConsoleImplementationPure } } -private object ConsoleInputImpl : ConsoleInput { - private val format = DateTimeFormatter.ofPattern("HH:mm:ss") - - override suspend fun requestInput(hint: String): String { - return withContext(Dispatchers.IO) { - lineReader.readLine( - if (hint.isNotEmpty()) { - lineReader.printAbove( - Ansi.ansi() - .fgCyan().a(LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).format(format)) - .a(" ") - .fgMagenta().a(hint) - .reset() - .toString() - ) - "$hint > " - } else "> " - ) - } - } -} - val lineReader: LineReader by lazy { - if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader + val terminal = terminal + if (terminal is NoConsole) return@lazy AllEmptyLineReader LineReaderBuilder.builder() .terminal(terminal) @@ -126,7 +106,7 @@ val lineReader: LineReader by lazy { } val terminal: Terminal = run { - if (ConsolePureSettings.noConsole) return@run NoConsole + if (ConsoleTerminalSettings.noConsole) return@run NoConsole val dumb = System.getProperty("java.class.path") .contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null @@ -134,7 +114,33 @@ val terminal: Terminal = run { runCatching { TerminalBuilder.builder() .dumb(dumb) + .paused(true) .build() + .let { terminal -> + if (terminal is AbstractWindowsTerminal) { + val pumpField = runCatching { + AbstractWindowsTerminal::class.java.getDeclaredField("pump").also { + it.isAccessible = true + } + }.onFailure { err -> + err.printStackTrace() + return@let terminal.also { it.resume() } + }.getOrThrow() + var response = terminal + terminal.setOnClose { + response = NoConsole + } + terminal.resume() + val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole + @Suppress("ControlFlowWithEmptyBody") + while (pumpThread.state == Thread.State.NEW); + Thread.sleep(1000) + terminal.setOnClose(null) + return@let response + } + terminal.resume() + terminal + } }.recoverCatching { TerminalBuilder.builder() .jansi(true) @@ -147,7 +153,7 @@ val terminal: Terminal = run { } private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { - override val name: String get() = "Pure" + override val name: String get() = "Terminal" override val vendor: String get() = "Mamoe Technologies" override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version } diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt similarity index 78% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt rename to frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt index 0fa6abe1f..2aa23e0b6 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt @@ -5,6 +5,7 @@ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. * * https://github.com/mamoe/mirai/blob/master/LICENSE + * */ @file:Suppress( @@ -15,9 +16,9 @@ "INVISIBLE_GETTER", "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", ) -@file:OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class) +@file:OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class) -package net.mamoe.mirai.console.pure +package net.mamoe.mirai.console.terminal import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -26,20 +27,22 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.data.AutoSavePluginDataHolder -import net.mamoe.mirai.console.pure.noconsole.SystemOutputPrintStream +import net.mamoe.mirai.console.terminal.noconsole.SystemOutputPrintStream import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.minutesToMillis +import java.io.FileDescriptor +import java.io.FileOutputStream import java.io.PrintStream import kotlin.system.exitProcess /** - * mirai-console-pure CLI 入口点 + * mirai-console-terminal CLI 入口点 */ -object MiraiConsolePureLoader { +object MiraiConsoleTerminalLoader { @JvmStatic fun main(args: Array) { parse(args, exitProcess = true) @@ -53,10 +56,10 @@ object MiraiConsolePureLoader { } } - @ConsolePureExperimentalApi + @ConsoleTerminalExperimentalApi fun printHelpMessage() { val help = listOf( - "" to "Mirai-Console[Pure FrontEnd] v" + kotlin.runCatching { + "" to "Mirai-Console[Terminal FrontEnd] v" + kotlin.runCatching { net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version }.getOrElse { "" }, "" to "", @@ -96,7 +99,7 @@ object MiraiConsolePureLoader { } } - @ConsolePureExperimentalApi + @ConsoleTerminalExperimentalApi fun parse(args: Array, exitProcess: Boolean = false) { val iterator = args.iterator() while (iterator.hasNext()) { @@ -107,19 +110,19 @@ object MiraiConsolePureLoader { return } "--no-console" -> { - ConsolePureSettings.noConsole = true + ConsoleTerminalSettings.noConsole = true } "--dont-setup-terminal-ansi" -> { - ConsolePureSettings.setupAnsi = false + ConsoleTerminalSettings.setupAnsi = false } "--no-ansi" -> { - ConsolePureSettings.noAnsi = true - ConsolePureSettings.setupAnsi = false + ConsoleTerminalSettings.noAnsi = true + ConsoleTerminalSettings.setupAnsi = false } "--reading-replacement" -> { - ConsolePureSettings.noConsoleSafeReading = true + ConsoleTerminalSettings.noConsoleSafeReading = true if (iterator.hasNext()) { - ConsolePureSettings.noConsoleReadingReplacement = iterator.next() + ConsoleTerminalSettings.noConsoleReadingReplacement = iterator.next() } else { println("Bad option `--reading-replacement`") println("Usage: --reading-replacement ") @@ -129,7 +132,7 @@ object MiraiConsolePureLoader { } } "--safe-reading" -> { - ConsolePureSettings.noConsoleSafeReading = true + ConsoleTerminalSettings.noConsoleSafeReading = true } else -> { println("Unknown option `$option`") @@ -140,13 +143,13 @@ object MiraiConsolePureLoader { } } } - if (ConsolePureSettings.noConsole) + if (ConsoleTerminalSettings.noConsole) SystemOutputPrintStream // Setup Output Channel } @Suppress("MemberVisibilityCanBePrivate") @ConsoleExperimentalApi - fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) { + fun startAsDaemon(instance: MiraiConsoleImplementationTerminal = MiraiConsoleImplementationTerminal()) { instance.start() overrideSTD() startupConsoleThread() @@ -160,7 +163,7 @@ internal object ConsoleDataHolder : AutoSavePluginDataHolder, @ConsoleExperimentalApi override val dataHolderName: String - get() = "Pure" + get() = "Terminal" } internal fun overrideSTD() { @@ -181,12 +184,16 @@ internal fun overrideSTD() { } -internal object ConsoleCommandSenderImplPure : MiraiConsoleImplementation.ConsoleCommandSenderImpl { +internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl { override suspend fun sendMessage(message: String) { kotlin.runCatching { lineReader.printAbove(message) - }.onFailure { - consoleLogger.error("Exception while ConsoleCommandSenderImplPure.sendMessage", it) + }.onFailure { exception -> + // If failed. It means JLine Terminal not working... + PrintStream(FileOutputStream(FileDescriptor.err)).use { + it.println("Exception while ConsoleCommandSenderImplTerminal.sendMessage") + exception.printStackTrace(it) + } } } diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/noconsole/NoConsole.kt similarity index 90% rename from frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt rename to frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/noconsole/NoConsole.kt index d04a69470..45a8be66d 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/noconsole/NoConsole.kt @@ -10,12 +10,12 @@ /* * @author Karlatemp */ -@file:OptIn(ConsolePureExperimentalApi::class) +@file:OptIn(ConsoleTerminalExperimentalApi::class) -package net.mamoe.mirai.console.pure.noconsole +package net.mamoe.mirai.console.terminal.noconsole -import net.mamoe.mirai.console.pure.ConsolePureExperimentalApi -import net.mamoe.mirai.console.pure.ConsolePureSettings +import net.mamoe.mirai.console.terminal.ConsoleTerminalExperimentalApi +import net.mamoe.mirai.console.terminal.ConsoleTerminalSettings import org.jline.keymap.KeyMap import org.jline.reader.* import org.jline.terminal.Attributes @@ -69,8 +69,8 @@ internal object AllIgnoredOutputStream : OutputStream() { } internal val SystemOutputPrintStream by lazy { - @OptIn(ConsolePureExperimentalApi::class) - if (ConsolePureSettings.setupAnsi) { + @OptIn(ConsoleTerminalExperimentalApi::class) + if (ConsoleTerminalSettings.setupAnsi) { org.fusesource.jansi.AnsiConsole.systemInstall() } System.out @@ -81,16 +81,16 @@ internal object AllEmptyLineReader : LineReader { override fun printAbove(str: String?) { if (str == null) return - @OptIn(ConsolePureExperimentalApi::class) - if (ConsolePureSettings.noAnsi) { + @OptIn(ConsoleTerminalExperimentalApi::class) + if (ConsoleTerminalSettings.noAnsi) { SystemOutputPrintStream.println(ANSI_REGEX.replace(str, "")) } else SystemOutputPrintStream.println(str) } - @OptIn(ConsolePureExperimentalApi::class) + @OptIn(ConsoleTerminalExperimentalApi::class) override fun readLine(): String = - if (ConsolePureSettings.noConsoleSafeReading) ConsolePureSettings.noConsoleReadingReplacement - else error("Unsupported Reading line when console front-end closed.") + if (ConsoleTerminalSettings.noConsoleSafeReading) ConsoleTerminalSettings.noConsoleReadingReplacement + else throw EndOfFileException("Unsupported Reading line when console front-end closed.") // region private fun ignored(): T = error("Ignored") diff --git a/settings.gradle.kts b/settings.gradle.kts index 27b9b3fc2..c69c29bf2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ fun includeProject(projectPath: String, path: String? = null) { includeProject(":mirai-console", "backend/mirai-console") includeProject(":mirai-console.codegen", "backend/codegen") -includeProject(":mirai-console-pure", "frontend/mirai-console-pure") +includeProject(":mirai-console-terminal", "frontend/mirai-console-terminal") includeProject(":mirai-console-compiler-common", "tools/compiler-common") includeProject(":mirai-console-intellij", "tools/intellij-plugin") includeProject(":mirai-console-gradle", "tools/gradle-plugin") diff --git a/tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt b/tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt index c4856993e..1e011b60a 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt @@ -2,11 +2,11 @@ import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.load -import net.mamoe.mirai.console.pure.MiraiConsolePureLoader +import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader import org.example.myplugin.MyPluginMain suspend fun main() { - MiraiConsolePureLoader.startAsDaemon() + MiraiConsoleTerminalLoader.startAsDaemon() MyPluginMain.load() // 主动加载插件, Console 会调用 MyPluginMain.onLoad MyPluginMain.enable() // 主动启用插件, Console 会调用 MyPluginMain.onEnable From 7244cb76c487818aaf818862217a7eb03002eb8b Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 15:36:03 +0800 Subject: [PATCH 029/114] Introduce inspection NOT_CONSTRUCTABLE_TYPE for PluginData.value --- .../console/compiler/common/ResolveContext.kt | 14 ++-- .../mamoe/mirai/console/data/PluginData.kt | 7 +- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../diagnostics/MiraiConsoleErrors.java | 1 + .../MiraiConsoleErrorsRendering.kt | 7 ++ .../common/resolve/MiraiConsoleTypes.kt | 7 ++ .../compiler/common/resolve/resolveCommon.kt | 35 +++++++++- .../projects/test-project/build.gradle.kts | 2 +- .../org/example/myplugin/MyPluginMain.kt | 25 ++++---- .../intellij/IDEContainerContributor.kt | 2 + .../diagnostics/PluginDataValuesChecker.kt | 64 +++++++++++++++++++ .../marker/PluginMainLineMarkerProvider.kt | 2 +- .../console/intellij/resolve/resolveIdea.kt | 30 ++++----- 13 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index fc41edc14..61e481370 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -12,16 +12,17 @@ package net.mamoe.mirai.console.compiler.common import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import kotlin.annotation.AnnotationTarget.* /** * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断. */ @ConsoleExperimentalApi @Target( - AnnotationTarget.VALUE_PARAMETER, - AnnotationTarget.PROPERTY, - AnnotationTarget.FIELD, - //AnnotationTarget.EXPRESSION + VALUE_PARAMETER, + PROPERTY, FIELD, + FUNCTION, + TYPE, TYPE_PARAMETER ) @Retention(AnnotationRetention.BINARY) public annotation class ResolveContext( @@ -34,5 +35,10 @@ public annotation class ResolveContext( PLUGIN_ID, PLUGIN_NAME, PLUGIN_VERSION, + + /** + * Custom serializers allowed + */ + RESTRICTED_NO_ARG_CONSTRUCTOR } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 7288eaa3d..4e67fdad3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -18,9 +18,13 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR import net.mamoe.mirai.console.data.java.JAutoSavePluginData import net.mamoe.mirai.console.internal.data.* +import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.plugin.jvm.reloadPluginData import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass @@ -98,7 +102,7 @@ import kotlin.reflect.full.findAnnotation * * 要查看详细的解释,请查看 [docs/PluginData.md](https://github.com/mamoe/mirai-console/blob/master/docs/PluginData.md) * - * @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. + * @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. * @see PluginDataStorage [PluginData] 存储仓库 * @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数 */ @@ -321,6 +325,7 @@ public inline fun PluginData.value( * 通过具体化类型创建一个 [SerializerAwareValue]. * @see valueFromKType 查看更多实现信息 */ +@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) @LowPriorityInOverloadResolution public inline fun PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue = valueImpl(typeOf0(), T::class).also { it.value.apply() } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 3349612e6..56e868d6d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-2" + const val console = "1.0-RC-dev-3" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" const val consolePure = console diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index b8e6b9be8..001c7d4d5 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -17,6 +17,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; public interface MiraiConsoleErrors { DiagnosticFactory1 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index a8af96814..6353a08cb 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.Renderers @@ -21,6 +22,12 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { "{0}", Renderers.STRING ) + + put( + NOT_CONSTRUCTABLE_TYPE, + "类型 {0} 无法通过反射直接构造, 需要提供默认值.", + Renderers.STRING + ) } override fun getMap() = MAP diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt index fa59374bd..18ae85598 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt @@ -30,6 +30,12 @@ val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") +/////////////////////////////////////////////////////////////////////////// +// PluginData +/////////////////////////////////////////////////////////////////////////// + +val PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME = FqName("net.mamoe.mirai.console.data.value") + /////////////////////////////////////////////////////////////////////////// // Resolve /////////////////////////////////////////////////////////////////////////// @@ -44,6 +50,7 @@ enum class ResolveContextKind { PLUGIN_NAME, PLUGIN_VERSION, + RESTRICTED_NO_ARG_CONSTRUCTOR ; companion object { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt index 4f2e6b05c..670ccba04 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveCommon.kt @@ -9,8 +9,41 @@ package net.mamoe.mirai.console.compiler.common.resolve +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqName) -fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName) \ No newline at end of file +fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName) + + +val PsiElement.allChildrenWithSelf: Sequence + get() = sequence { + yield(this@allChildrenWithSelf) + for (child in children) { + yieldAll(child.allChildrenWithSelf) + } + } + + +inline fun PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() + + +val PsiElement.parents: Sequence + get() { + val seed = if (this is PsiFile) null else parent + return generateSequence(seed) { if (it is PsiFile) null else it.parent } + } + + +fun ClassDescriptor.findNoArgConstructor(): ClassConstructorDescriptor? { + return constructors.find { desc -> + desc.valueParameters.all { it.hasDefaultValue() } + } +} + +fun ClassDescriptor.hasNoArgConstructor(): Boolean = this.findNoArgConstructor() != null \ No newline at end of file diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index d26b54d1d..4e4358b16 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { compileOnly(kotlin("stdlib-jdk8")) val core = "1.3.0" - val console = "1.0-RC-dev-2" + val console = "1.0-RC-dev-3" compileOnly("net.mamoe:mirai-console:$console") compileOnly("net.mamoe:mirai-core:$core") diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 9f1bdcbd5..5ab9b553e 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -1,5 +1,7 @@ package org.example.myplugin +import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin @@ -19,16 +21,15 @@ object MyPluginMain : KotlinPlugin( } } -object MyPluginMain2 : KotlinPlugin( - JvmPluginDescription( - "", - "0.1.0", - ) { - name(".") - id("") - } -) { - fun test() { +object DataTest : AutoSavePluginConfig() { + val p by value() + val pp by value() +} - } -} \ No newline at end of file +data class HasDefaultValue( + val x: Int = 0, +) + +data class NoDefaultValue( + val y: Int, +) \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt index 7e023b444..24cc9ef81 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.console.intellij +import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useInstance @@ -22,5 +23,6 @@ class IDEContainerContributor : StorageComponentContainerContributor { moduleDescriptor: ModuleDescriptor, ) { container.useInstance(PluginDescriptionChecker()) + container.useInstance(PluginDataValuesChecker()) } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt new file mode 100644 index 000000000..9b0552829 --- /dev/null +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij.diagnostics + +import com.intellij.psi.PsiElement +import net.mamoe.mirai.console.compiler.common.castOrNull +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors +import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME +import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind +import net.mamoe.mirai.console.compiler.common.resolve.hasNoArgConstructor +import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind +import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.idea.refactoring.fqName.fqName +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.types.SimpleType + + +class PluginDataValuesChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + val bindingContext = context.bindingContext + declaration.resolveAllCallsWithElement(bindingContext) + .filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) } + .filter { (call) -> + call.resultingDescriptor.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR + }.flatMap { (call, element) -> + call.typeArguments.entries.associateWith { element }.asSequence() + }.filter { (e, _) -> + val (p, t) = e + (p.isReified || p.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) + && t is SimpleType + }.forEach { (e, callExpr) -> + val (_, t) = e + val classDescriptor = t.constructor.declarationDescriptor?.castOrNull() + + fun getInspectionTarget(): PsiElement { + return callExpr.typeArguments.find { it.references.firstOrNull()?.canonicalText == t.fqName?.toString() } ?: callExpr + } + + fun reportInspection() { + context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on( + getInspectionTarget(), + t.fqName?.asString().toString()) + ) + } + + when { + classDescriptor == null -> reportInspection() + !classDescriptor.hasNoArgConstructor() -> reportInspection() + } + } + } +} \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt index f941bea98..b69cd1f4d 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/line/marker/PluginMainLineMarkerProvider.kt @@ -17,10 +17,10 @@ import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import com.intellij.util.castSafelyTo import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME +import net.mamoe.mirai.console.compiler.common.resolve.parents import net.mamoe.mirai.console.intellij.Icons import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark -import net.mamoe.mirai.console.intellij.resolve.parents import org.jetbrains.kotlin.nj2k.postProcessing.resolve import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtConstructor diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index d2e240cd4..4d3614456 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -11,10 +11,11 @@ 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 import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME +import net.mamoe.mirai.console.compiler.common.resolve.allChildrenWithSelf +import net.mamoe.mirai.console.compiler.common.resolve.findParent import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor @@ -67,16 +68,8 @@ fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { return null } -inline fun PsiElement.findParent(): E? = this.parents.filterIsInstance().firstOrNull() - val KtClassOrObject.allSuperNames: Sequence get() = allSuperTypes.mapNotNull { it.getKotlinFqName() } -val PsiElement.parents: Sequence - get() { - val seed = if (this is PsiFile) null else parent - return generateSequence(seed) { if (it is PsiFile) null else it.parent } - } - fun getElementForLineMark(callElement: PsiElement): PsiElement = when (callElement) { is KtSimpleNameExpression -> callElement.getReferencedNameElement() @@ -92,20 +85,23 @@ val KtAnnotationEntry.annotationClass: KtClass? fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = this.annotationEntries.any { it.annotationClass?.getKotlinFqName() == fqName } -val PsiElement.allChildrenWithSelf: Sequence - get() = sequence { - yield(this@allChildrenWithSelf) - for (child in children) { - yieldAll(child.allChildrenWithSelf) - } - } - fun KtDeclaration.resolveAllCalls(bindingContext: BindingContext): Sequence> { return allChildrenWithSelf .filterIsInstance() .mapNotNull { it.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext) } } +fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Sequence, KtCallExpression>> { + return allChildrenWithSelf + .filterIsInstance() + .mapNotNull { + val callee = it.calleeExpression ?: return@mapNotNull null + val resolved = callee.getResolvedCallOrResolveToCall(bindingContext) ?: return@mapNotNull null + + resolved to it + } +} + fun ResolvedCall<*>.valueParametersWithArguments(): List> { return this.valueParameters.zip(this.valueArgumentsByIndex?.mapNotNull { it.arguments.firstOrNull() }.orEmpty()) } From 39145634e6184b05d188219e11651d7436f42e34 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 20:00:47 +0800 Subject: [PATCH 030/114] Support UNSERIALIZABLE_TYPE for detecting `@Serializable` annotations --- .../diagnostics/MiraiConsoleErrors.java | 1 + .../MiraiConsoleErrorsRendering.kt | 9 +++-- .../console/compiler/common/utilCommon.kt | 2 ++ .../org/example/myplugin/MyPluginMain.kt | 2 ++ .../diagnostics/PluginDataValuesChecker.kt | 35 +++++++++---------- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index 001c7d4d5..fcf17ee32 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -18,6 +18,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; public interface MiraiConsoleErrors { DiagnosticFactory1 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 UNSERIALIZABLE_TYPE = DiagnosticFactory1.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index 6353a08cb..ddab8060e 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -9,8 +9,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics -import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION -import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.* import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.Renderers @@ -28,6 +27,12 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { "类型 {0} 无法通过反射直接构造, 需要提供默认值.", Renderers.STRING ) + + put( + UNSERIALIZABLE_TYPE, + "类型 {0} 无法被自动序列化, 需要添加序列化器", + Renderers.STRING + ) } override fun getMap() = MAP diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt index 8bdabfe92..4726abb9f 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/utilCommon.kt @@ -9,8 +9,10 @@ package net.mamoe.mirai.console.compiler.common +import org.jetbrains.kotlin.name.FqName import kotlin.contracts.contract +val SERIALIZABLE_FQ_NAME = FqName("kotlinx.serialization.Serializable") fun Map.firstValue(): V = this.entries.first().value fun Map.firstKey(): K = this.entries.first().key diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 5ab9b553e..7dd80641a 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -1,5 +1,6 @@ package org.example.myplugin +import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription @@ -26,6 +27,7 @@ object DataTest : AutoSavePluginConfig() { val pp by value() } +@Serializable data class HasDefaultValue( val x: Int = 0, ) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt index 9b0552829..16e6e2cf8 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt @@ -10,12 +10,10 @@ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement +import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors -import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME -import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind -import net.mamoe.mirai.console.compiler.common.resolve.hasNoArgConstructor -import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind +import net.mamoe.mirai.console.compiler.common.resolve.* import net.mamoe.mirai.console.intellij.resolve.resolveAllCallsWithElement import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor @@ -41,24 +39,25 @@ class PluginDataValuesChecker : DeclarationChecker { (p.isReified || p.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) && t is SimpleType }.forEach { (e, callExpr) -> - val (_, t) = e - val classDescriptor = t.constructor.declarationDescriptor?.castOrNull() + val (_, type) = e + val classDescriptor = type.constructor.declarationDescriptor?.castOrNull() - fun getInspectionTarget(): PsiElement { - return callExpr.typeArguments.find { it.references.firstOrNull()?.canonicalText == t.fqName?.toString() } ?: callExpr + val inspectionTarget: PsiElement by lazy { + callExpr.typeArguments.find { it.references.firstOrNull()?.canonicalText == type.fqName?.toString() } ?: callExpr } - fun reportInspection() { - context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on( - getInspectionTarget(), - t.fqName?.asString().toString()) + if (classDescriptor == null + || !classDescriptor.hasNoArgConstructor() + ) return@forEach context.report(MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on( + inspectionTarget, + type.fqName?.asString().toString()) + ) + + if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME)) // TODO: 2020/9/18 external serializers + return@forEach context.report(MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on( + inspectionTarget, + type.fqName?.asString().toString()) ) - } - - when { - classDescriptor == null -> reportInspection() - !classDescriptor.hasNoArgConstructor() -> reportInspection() - } } } } \ No newline at end of file From 179c89b48fe124f2f5370bbc1b9066194d94975e Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 20:06:03 +0800 Subject: [PATCH 031/114] Fix Pair and Triple values --- .../net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt index feec64e53..037c65ba5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt @@ -143,7 +143,7 @@ internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean { Byte::class, Short::class, Int::class, Long::class, Boolean::class, Char::class, String::class, - Pair::class, Triple::class // TODO: 2020/6/24 支持 PairValue, TripleValue + //Pair::class, Triple::class // TODO: 2020/6/24 支持 PairValue, TripleValue -> return true } From 53890bcb5d384c109a936023f1d6d2ca4565e9e1 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 20:19:39 +0800 Subject: [PATCH 032/114] API stabilization: move provideDelegate from PluginData to AbstractPluginData --- .../mirai/console/data/AbstractPluginData.kt | 22 ++++++++++++++++++- .../mamoe/mirai/console/data/PluginData.kt | 8 ------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt index 882e6bb02..ce55fae6c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt @@ -14,6 +14,9 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer import net.mamoe.mirai.console.data.PluginData.ValueNode import net.mamoe.mirai.console.internal.data.PluginDataImpl +import net.mamoe.mirai.console.internal.data.getAnnotationListForValueSerialization +import net.mamoe.mirai.console.internal.data.valueName +import kotlin.reflect.KProperty /** * [PluginData] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动. @@ -36,6 +39,14 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { public override fun > T.track(valueName: String, annotations: List): T = apply { valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) } + /** + * 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes] + */ + public operator fun > T.provideDelegate( + thisRef: Any?, + property: KProperty<*>, + ): T = track(property.valueName, property.getAnnotationListForValueSerialization()) + /** * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 */ @@ -45,5 +56,14 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ - public abstract override fun onValueChanged(value: Value<*>) + public override fun onValueChanged(value: Value<*>) { + // no-op by default + } + + /** + * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 + */ + public override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { + // no-op by default + } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 4e67fdad3..28f27c62c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -155,14 +155,6 @@ public interface PluginData { val updaterSerializer: KSerializer ) - /** - * 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes] - */ - public operator fun > T.provideDelegate( - thisRef: Any?, - property: KProperty<*> - ): T = track(property.valueName, property.getAnnotationListForValueSerialization()) - /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ From 2fe2d2a681d70ff87cee25a53e55e26ce8661a22 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 20:35:36 +0800 Subject: [PATCH 033/114] API stabilization for PluginData: Move members valueNodes, ValueNode, track from PluginData to AbstractPluginData; Move findBackingFieldValue, findBackingFieldValueNode from PluginData.kt to AbstractPluginData.kt; Mark unstable APIs with `@ConsoleExperimentalApi` --- .../mirai/console/data/AbstractPluginData.kt | 111 ++++++++++++++++- .../mirai/console/data/AutoSavePluginData.kt | 1 + .../mamoe/mirai/console/data/PluginData.kt | 117 +----------------- .../mirai/console/data/PluginDataStorage.kt | 1 + .../net/mamoe/mirai/console/data/ValueName.kt | 3 + .../data/MultiFilePluginDataStorageImpl.kt | 9 +- .../console/internal/data/PluginDataImpl.kt | 2 +- 7 files changed, 121 insertions(+), 123 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt index ce55fae6c..080170e9f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt @@ -12,10 +12,10 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer -import net.mamoe.mirai.console.data.PluginData.ValueNode import net.mamoe.mirai.console.internal.data.PluginDataImpl import net.mamoe.mirai.console.internal.data.getAnnotationListForValueSerialization import net.mamoe.mirai.console.internal.data.valueName +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.reflect.KProperty /** @@ -31,13 +31,26 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { * * @see provideDelegate */ + @ConsoleExperimentalApi public override val valueNodes: MutableList> = mutableListOf() /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ - public override fun > T.track(valueName: String, annotations: List): T = - apply { valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) } + @ConsoleExperimentalApi + public fun > track( + value: T, + /** + * 值名称. + * + * 如果属性带有 [ValueName], 则使用 [ValueName.value], + * 否则使用 [属性名称][KProperty.name] + * + * @see [ValueNode.value] + */ + valueName: String, + annotations: List, + ): T = value.apply { this@AbstractPluginData.valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) } /** * 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes] @@ -45,17 +58,19 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { public operator fun > T.provideDelegate( thisRef: Any?, property: KProperty<*>, - ): T = track(property.valueName, property.getAnnotationListForValueSerialization()) + ): T = track(this, property.valueName, property.getAnnotationListForValueSerialization()) /** * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 */ + @ConsoleExperimentalApi public final override val updaterSerializer: KSerializer get() = super.updaterSerializer /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ + @ConsoleExperimentalApi public override fun onValueChanged(value: Value<*>) { // no-op by default } @@ -63,7 +78,95 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 */ + @ConsoleExperimentalApi public override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { // no-op by default } + + /** + * 由 [track] 创建, 来自一个通过 `by value` 初始化的属性节点. + */ + @ConsoleExperimentalApi + public data class ValueNode( + /** + * 节点名称. + * + * 如果属性带有 [ValueName], 则使用 [ValueName.value], + * 否则使用 [属性名称][KProperty.name] + */ + val valueName: String, + /** + * 属性值代理 + */ + val value: Value, + /** + * 注解列表 + */ + val annotations: List, + /** + * 属性值更新器 + */ + val updaterSerializer: KSerializer, + ) +} + +/** + * 获取这个 [KProperty] 委托的 [Value] + * + * 示例: + * ``` + * object MyData : AutoSavePluginData(PluginMain) { + * val list: List by value() + * } + * + * val value: Value> = MyData.findBackingFieldValue(MyData::list) + * ``` + * + * @see PluginData + */ +@ConsoleExperimentalApi +public fun AbstractPluginData.findBackingFieldValue(property: KProperty): Value? = + findBackingFieldValue(property.valueName) + +/** + * 获取这个 [KProperty] 委托的 [Value] + * + * 示例: + * ``` + * object MyData : AutoSavePluginData(PluginMain) { + * @ValueName("theList") + * val list: List by value() + * val int: Int by value() + * } + * + * val value: Value> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称 + * val intValue: Value = MyData.findBackingFieldValue("int") + * ``` + * + * @see PluginData + */ +@ConsoleExperimentalApi +public fun AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value? { + @Suppress("UNCHECKED_CAST") + return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value +} + +/** + * 获取这个 [KProperty] 委托的 [Value] + * + * 示例: + * ``` + * object MyData : AutoSavePluginData(PluginMain) { + * val list: List by value() + * } + * + * val value: PluginData.ValueNode> = MyData.findBackingFieldValueNode(MyData::list) + * ``` + * + * @see PluginData + */ +@ConsoleExperimentalApi +public fun AbstractPluginData.findBackingFieldValueNode(property: KProperty): AbstractPluginData.ValueNode? { + @Suppress("UNCHECKED_CAST") + return this.valueNodes.find { it == property } as AbstractPluginData.ValueNode? } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt index 85e45bc10..937a32de4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -121,6 +121,7 @@ public open class AutoSavePluginData private constructor( } } + @ConsoleExperimentalApi public final override fun onValueChanged(value: Value<*>) { debuggingLogger1.error { "onValueChanged: $value" } if (::owner_.isInitialized) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 28f27c62c..705c90996 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -21,14 +21,16 @@ import kotlinx.serialization.KSerializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR import net.mamoe.mirai.console.data.java.JAutoSavePluginData -import net.mamoe.mirai.console.internal.data.* +import net.mamoe.mirai.console.internal.data.createInstanceSmart +import net.mamoe.mirai.console.internal.data.typeOf0 +import net.mamoe.mirai.console.internal.data.valueFromKTypeImpl +import net.mamoe.mirai.console.internal.data.valueImpl import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.reloadPluginData import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass -import kotlin.reflect.KProperty import kotlin.reflect.KType import kotlin.reflect.full.findAnnotation @@ -107,16 +109,6 @@ import kotlin.reflect.full.findAnnotation * @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数 */ public interface PluginData { - /** - * 添加了追踪的 [ValueNode] 列表 (即使用 `by value()` 委托的属性), 即通过 `by value` 初始化的属性列表. - * - * 他们的修改会被跟踪, 并触发 [onValueChanged]. - * - * @see provideDelegate - * @see track - */ - public val valueNodes: MutableList> - /** * 这个 [PluginData] 保存时使用的名称. 默认通过 [ValueName] 获取, 否则使用 [类全名][KClass.qualifiedName] (即 [Class.getCanonicalName]) */ @@ -129,56 +121,13 @@ public interface PluginData { ?: throw IllegalArgumentException("Cannot find a serial name for ${this::class}") } - /** - * 由 [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点. - */ @ConsoleExperimentalApi - public data class ValueNode( - /** - * 节点名称. - * - * 如果属性带有 [ValueName], 则使用 [ValueName.value], - * 否则使用 [属性名称][KProperty.name] - */ - val valueName: String, - /** - * 属性值代理 - */ - val value: Value, - /** - * 注解列表 - */ - val annotations: List, - /** - * 属性值更新器 - */ - val updaterSerializer: KSerializer - ) - - /** - * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] - */ - public fun > T.track( - /** - * 值名称. - * - * 如果属性带有 [ValueName], 则使用 [ValueName.value], - * 否则使用 [属性名称][KProperty.name] - * - * @see [ValueNode.value] - */ - valueName: String, - annotations: List - ): T - - /** - * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 - */ public val updaterSerializer: KSerializer /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ + @ConsoleExperimentalApi public fun onValueChanged(value: Value<*>) /** @@ -188,62 +137,6 @@ public interface PluginData { public fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) } -/** - * 获取这个 [KProperty] 委托的 [Value] - * - * 如, 对于 - * ``` - * object MyData : AutoSavePluginData(PluginMain) { - * val list: List by value() - * } - * - * val value: Value> = MyData.findBackingFieldValue(MyData::list) - * ``` - * - * @see PluginData - */ -public fun PluginData.findBackingFieldValue(property: KProperty): Value? = - findBackingFieldValue(property.valueName) - -/** - * 获取这个 [KProperty] 委托的 [Value] - * - * 如, 对于 - * ``` - * object MyData : AutoSavePluginData(PluginMain) { - * @ValueName("theList") - * val list: List by value() - * val int: Int by value() - * } - * - * val value: Value> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称 - * val intValue: Value = MyData.findBackingFieldValue("int") - * ``` - * - * @see PluginData - */ -public fun PluginData.findBackingFieldValue(propertyValueName: String): Value? { - return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value -} - - -/** - * 获取这个 [KProperty] 委托的 [Value] - * - * 如, 对于 - * ``` - * object MyData : AutoSavePluginData(PluginMain) { - * val list: List by value() - * } - * - * val value: PluginData.ValueNode> = MyData.findBackingFieldValueNode(MyData::list) - * ``` - * - * @see PluginData - */ -public fun PluginData.findBackingFieldValueNode(property: KProperty): PluginData.ValueNode? { - return this.valueNodes.find { it == property } as PluginData.ValueNode? -} // don't default = 0, cause ambiguity //// region PluginData_value_primitives CODEGEN //// diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt index 2f09d6f09..8e4e667ac 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt @@ -135,6 +135,7 @@ public interface MultiFilePluginDataStorage : PluginDataStorage { } } +@ConsoleExperimentalApi @get:JvmSynthetic public inline val MultiFilePluginDataStorage.directory: File get() = this.directoryPath.toFile() \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt index 9737bf73a..d1017080b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt @@ -9,6 +9,8 @@ package net.mamoe.mirai.console.data +import net.mamoe.mirai.console.util.ConsoleExperimentalApi + /** * 序列化之后的名称. * @@ -27,6 +29,7 @@ package net.mamoe.mirai.console.data * a: b * ``` */ +@ConsoleExperimentalApi @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) public annotation class ValueName(val value: String) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt index a753a52cd..351ea18ea 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt @@ -9,10 +9,7 @@ package net.mamoe.mirai.console.internal.data -import net.mamoe.mirai.console.data.MultiFilePluginDataStorage -import net.mamoe.mirai.console.data.PluginData -import net.mamoe.mirai.console.data.PluginDataHolder -import net.mamoe.mirai.console.data.PluginDataStorage +import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiLogger @@ -42,7 +39,7 @@ internal open class MultiFilePluginDataStorageImpl( } else { this.store(holder, instance) // save an initial copy } - logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" } + logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull()?.valueNodes?.size} properties)" } } protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File { @@ -83,7 +80,7 @@ internal open class MultiFilePluginDataStorageImpl( throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it) } ) - logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" } + logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull()?.valueNodes?.size} properties)" } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt index 4dc70fa25..d388d87fc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt @@ -18,8 +18,8 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import net.mamoe.mirai.console.data.AbstractPluginData.ValueNode import net.mamoe.mirai.console.data.PluginData -import net.mamoe.mirai.console.data.PluginData.ValueNode import net.mamoe.mirai.console.data.Value import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.ValueName From da68027b7c8568c0499d66a7dd8f0067ab363eeb Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 20:53:08 +0800 Subject: [PATCH 034/114] API stabilization for PluginData: Enforce explicit saveName on init --- .../mirai/console/data/AbstractPluginData.kt | 9 +++-- .../console/data/AutoSavePluginConfig.kt | 8 ++++- .../mirai/console/data/AutoSavePluginData.kt | 17 ++++++++- .../mamoe/mirai/console/data/PluginData.kt | 18 ++++------ .../data/java/JAutoSavePluginConfig.kt | 8 ++++- .../console/data/java/JAutoSavePluginData.kt | 8 ++++- .../console/internal/data/PluginDataImpl.kt | 35 ++++++++++--------- .../internal/data/builtins/AutoLoginConfig.kt | 5 +-- .../BuiltInSingletonExtensionSelector.kt | 4 +-- .../permission/BuiltInPermissionServices.kt | 8 ++--- .../mamoe/mirai/console/data/SettingTest.kt | 2 +- 11 files changed, 76 insertions(+), 46 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt index 080170e9f..72ca966b2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt @@ -24,6 +24,11 @@ import kotlin.reflect.KProperty * @see PluginData */ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { + /** + * 这个 [PluginData] 保存时使用的名称. + */ + public abstract override val saveName: String + /** * 添加了追踪的 [ValueNode] 列表, 即通过 `by value` 初始化的属性列表. * @@ -32,7 +37,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { * @see provideDelegate */ @ConsoleExperimentalApi - public override val valueNodes: MutableList> = mutableListOf() + public val valueNodes: MutableList> = mutableListOf() /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] @@ -61,7 +66,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { ): T = track(this, property.valueName, property.getAnnotationListForValueSerialization()) /** - * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 + * 所有 [valueNodes] 更新和保存序列化器. */ @ConsoleExperimentalApi public final override val updaterSerializer: KSerializer diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt index f6aa40dfa..df4a4c280 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt @@ -23,4 +23,10 @@ import kotlinx.coroutines.Job * @see PluginConfig * @see AutoSavePluginData */ -public open class AutoSavePluginConfig : AutoSavePluginData(), PluginConfig \ No newline at end of file +public open class AutoSavePluginConfig : AutoSavePluginData, PluginConfig { + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"改成保存的名称\")")) + @Suppress("DEPRECATION_ERROR") + public constructor() : super() + + public constructor(saveName: String) : super(saveName) +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt index 937a32de4..06410a113 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -18,6 +18,7 @@ import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.plugin.updateWhen import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.* +import kotlin.reflect.full.findAnnotation /** * 链接自动保存的 [PluginData]. @@ -35,8 +36,22 @@ public open class AutoSavePluginData private constructor( private val autoSaveIntervalMillis_: LongRange get() = owner_.autoSaveIntervalMillis private lateinit var storage_: PluginDataStorage - public constructor() : this(null) + public final override val saveName: String + get() = _saveName + private lateinit var _saveName: String + + public constructor(saveName: String) : this(null) { + _saveName = saveName + } + + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig")) + public constructor() : this(null) { + val clazz = this::class + _saveName = clazz.findAnnotation()?.value + ?: clazz.qualifiedName + ?: throw IllegalArgumentException("Cannot find a serial name for ${this::class}") + } @ConsoleExperimentalApi override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 705c90996..119b9f2e3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -32,12 +32,11 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.KType -import kotlin.reflect.full.findAnnotation /** - * 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. + * 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. 典型的实现为 [AbstractPluginData]. * - * [PluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][valueNodes]. + * [AbstractPluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][AbstractPluginData.valueNodes]. * * 有关存储方案, 请查看 [PluginDataStorage]. * @@ -74,7 +73,7 @@ import kotlin.reflect.full.findAnnotation * val theList: MutableList = AccountPluginData.list * ``` * - * 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. 若必要存储, 请使用 [PluginData.findBackingFieldValue] + * 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. 若必要存储, 请使用 [AbstractPluginData.findBackingFieldValue] * * ### 使用 Java * @@ -110,22 +109,17 @@ import kotlin.reflect.full.findAnnotation */ public interface PluginData { /** - * 这个 [PluginData] 保存时使用的名称. 默认通过 [ValueName] 获取, 否则使用 [类全名][KClass.qualifiedName] (即 [Class.getCanonicalName]) + * 这个 [PluginData] 保存时使用的名称. */ @ConsoleExperimentalApi public val saveName: String - get() { - val clazz = this::class - return clazz.findAnnotation()?.value - ?: clazz.qualifiedName - ?: throw IllegalArgumentException("Cannot find a serial name for ${this::class}") - } @ConsoleExperimentalApi public val updaterSerializer: KSerializer /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. + * 调用者为 [Value] 的实现. */ @ConsoleExperimentalApi public fun onValueChanged(value: Value<*>) @@ -202,7 +196,7 @@ public fun PluginData.value(default: String): SerializerAwareValue = val @LowPriorityInOverloadResolution public inline fun PluginData.value( default: T, - crossinline apply: T.() -> Unit = {} + crossinline apply: T.() -> Unit = {}, ): SerializerAwareValue = valueFromKType(typeOf0(), default).also { it.value.apply() } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt index 16a7764eb..5d9788dd1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt @@ -37,4 +37,10 @@ import net.mamoe.mirai.console.data.PluginData * @see JAutoSavePluginData * @see PluginConfig */ -public abstract class JAutoSavePluginConfig : AutoSavePluginConfig(), PluginConfig +public abstract class JAutoSavePluginConfig : AutoSavePluginConfig, PluginConfig { + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"改成保存的名称\")")) + @Suppress("DEPRECATION_ERROR") + public constructor() : super() + + public constructor(saveName: String) : super(saveName) +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt index 835522027..ec2986fbc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt @@ -66,7 +66,13 @@ import kotlin.reflect.full.createType * * @see PluginData */ -public abstract class JAutoSavePluginData : AutoSavePluginData(), PluginConfig { +public abstract class JAutoSavePluginData : AutoSavePluginData, PluginConfig { + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"改成保存的名称\")")) + @Suppress("DEPRECATION_ERROR") + public constructor() : super() + + public constructor(saveName: String) : super(saveName) + //// region JPluginData_value_primitives CODEGEN //// /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt index d388d87fc..e52d4d79a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt @@ -18,9 +18,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import net.mamoe.mirai.console.data.AbstractPluginData import net.mamoe.mirai.console.data.AbstractPluginData.ValueNode import net.mamoe.mirai.console.data.PluginData -import net.mamoe.mirai.console.data.Value import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.ValueName import net.mamoe.yamlkt.Comment @@ -34,12 +34,25 @@ import kotlin.reflect.KAnnotatedElement * - Auto-saving */ internal abstract class PluginDataImpl { - internal fun findNodeInstance(name: String): ValueNode<*>? = valueNodes.firstOrNull { it.valueName == name } + init { + @Suppress("LeakingThis") + check(this is AbstractPluginData) + } - internal abstract val valueNodes: MutableList> + private fun findNodeInstance(name: String): ValueNode<*>? { + check(this is AbstractPluginData) + return valueNodes.firstOrNull { it.valueName == name } + } internal open val updaterSerializer: KSerializer = object : KSerializer { - override val descriptor: SerialDescriptor get() = dataUpdaterSerializerDescriptor + override val descriptor: SerialDescriptor by lazy { + check(this@PluginDataImpl is AbstractPluginData) + kotlinx.serialization.descriptors.buildClassSerialDescriptor((this@PluginDataImpl as PluginData).saveName) { + for (valueNode in valueNodes) valueNode.run { + element(valueName, updaterSerializer.descriptor, annotations = annotations, isOptional = true) + } + } + } @Suppress("UNCHECKED_CAST") override fun deserialize(decoder: Decoder) { @@ -84,6 +97,8 @@ internal abstract class PluginDataImpl { @Suppress("UNCHECKED_CAST") override fun serialize(encoder: Encoder, value: Unit) { + check(this@PluginDataImpl is AbstractPluginData) + val descriptor = descriptor with(encoder.beginStructure(descriptor)) { repeat(descriptor.elementsCount) { index -> @@ -100,18 +115,6 @@ internal abstract class PluginDataImpl { } } - - /** - * flatten - */ - abstract fun onValueChanged(value: Value<*>) - private val dataUpdaterSerializerDescriptor by lazy { - kotlinx.serialization.descriptors.buildClassSerialDescriptor((this as PluginData).saveName) { - for (valueNode in valueNodes) valueNode.run { - element(valueName, updaterSerializer.descriptor, annotations = annotations, isOptional = true) - } - } - } } internal fun KAnnotatedElement.getAnnotationListForValueSerialization(): List { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtins/AutoLoginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtins/AutoLoginConfig.kt index 3fee86f5b..5cf269874 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtins/AutoLoginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtins/AutoLoginConfig.kt @@ -6,10 +6,7 @@ import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.internal.util.md5 import net.mamoe.mirai.console.internal.util.toUHexString -internal object AutoLoginConfig : AutoSavePluginConfig() { - override val saveName: String - get() = "AutoLogin" - +internal object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") { @ValueDescription( """ 账号和明文密码列表 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt index 5b50e64ca..60bb09cd8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extension/BuiltInSingletonExtensionSelector.kt @@ -16,9 +16,7 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector { internal val config: SaveData = SaveData() - internal class SaveData : AutoSavePluginConfig() { - override val saveName: String get() = "ExtensionSelector" - + internal class SaveData : AutoSavePluginConfig("ExtensionSelector") { val value: MutableMap by value() } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt index 2d2271312..d787e3358 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt @@ -117,12 +117,12 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService

> - by value>>(ConcurrentHashMap()) - .withDefault { CopyOnWriteArraySet() } + by value>>(ConcurrentHashMap()) + .withDefault { CopyOnWriteArraySet() } public companion object { @JvmStatic diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt index 22c052765..e50cb7e11 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/data/SettingTest.kt @@ -17,7 +17,7 @@ import kotlin.test.assertSame @OptIn(ConsoleInternalApi::class) internal class PluginDataTest { - class MyPluginData : AutoSavePluginData() { + class MyPluginData : AutoSavePluginData("test") { var int by value(1) val map: MutableMap by value() val map2: MutableMap> by value() From f2d8b1620eb405ca54de96bf9453b8a1889f5c67 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 21:10:11 +0800 Subject: [PATCH 035/114] API stabilization for PluginData: Add warnings for PluginConfig --- .../kotlin/net/mamoe/mirai/console/data/PluginConfig.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt index df2381c75..88f74e033 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt @@ -37,4 +37,10 @@ import net.mamoe.mirai.console.data.java.JAutoSavePluginConfig * * @see PluginData */ -public interface PluginConfig : PluginData \ No newline at end of file +public interface PluginConfig : PluginData { + /** + * 警告: [PluginConfig] 的实现处于实验性阶段. + * + * 自主实现 [PluginConfig] 将得不到兼容性保障. 请仅考虑使用 [AutoSavePluginConfig] + */ +} \ No newline at end of file From 8654172872c45e3036291eae040ab50f26894181 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 21:10:38 +0800 Subject: [PATCH 036/114] Remove redundant `@JvmDefault` --- .../kotlin/net/mamoe/mirai/console/command/CommandManager.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index a14c70c39..1e2af470c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -116,7 +116,6 @@ public interface CommandManager { * @return 执行结果 * @see executeCommand */ - @JvmDefault @JvmBlockingBridge public suspend fun CommandSender.executeCommand( message: String, @@ -139,7 +138,6 @@ public interface CommandManager { * 执行一个确切的指令 * @see executeCommand 获取更多信息 */ - @JvmDefault @JvmBlockingBridge @JvmName("executeCommand") public suspend fun Command.execute( From 899c6266ddbfeaef817666b72dccd92409f04838 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 21:21:06 +0800 Subject: [PATCH 037/114] Improve docs --- .../net/mamoe/mirai/console/command/Command.kt | 16 +++++++++++----- .../mirai/console/command/CommandManager.kt | 14 +++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index a746b50e8..c89117904 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -19,7 +19,6 @@ import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.internal.command.isValidSubName import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.SingleMessage /** * 指令 @@ -36,7 +35,9 @@ public interface Command { /** * 指令名. 需要至少有一个元素. 所有元素都不能带有空格 * - * @see Command.primaryName 获取主要指令名 + * 第一个元素会作为主指令名. + * + * @see Command.primaryName 获取主指令名 */ public val names: Array @@ -56,7 +57,9 @@ public interface Command { public val permission: Permission /** - * 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 + * 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选. + * + * 会影响消息语境中的解析. */ public val prefixOptional: Boolean @@ -69,7 +72,7 @@ public interface Command { /** * 在指令被执行时调用. * - * @param args 指令参数. 数组元素类型可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割. + * @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数. * * @see CommandManager.executeCommand 查看更多信息 */ @@ -86,6 +89,9 @@ public interface Command { } } +/** + * 调用 [Command.onCommand] + */ @JvmSynthetic public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit = sender.onCommand(args) @@ -108,7 +114,7 @@ public abstract class AbstractCommand public override val prefixOptional: Boolean = false, ) : Command { public override val description: String = description.trimIndent() - public override val names: Array = + public final override val names: Array = names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list -> list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") } }.toTypedArray() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 1e2af470c..ba6c696a1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -26,11 +26,15 @@ import net.mamoe.mirai.message.data.* public interface CommandManager { /** * 获取已经注册了的属于这个 [CommandOwner] 的指令列表. + * + * @return 这一时刻的浅拷贝. */ public val CommandOwner.registeredCommands: List /** * 获取所有已经注册了指令列表. + * + * @return 这一时刻的浅拷贝. */ public val allRegisteredCommands: List @@ -71,19 +75,23 @@ public interface CommandManager { public fun Command.findDuplicate(): Command? /** - * 取消注册这个指令. 若指令未注册, 返回 `false`. + * 取消注册这个指令. + * + * 若指令未注册, 返回 `false`. */ @JvmName("unregisterCommand") public fun Command.unregister(): Boolean /** - * 当 [this] 已经 [注册][register] 后返回 `true` + * 当 [this] 已经 [注册][register] 时返回 `true` */ @JvmName("isCommandRegistered") public fun Command.isRegistered(): Boolean /** - * 解析并执行一个指令 + * 解析并执行一个指令. + * + * 如要避免参数解析, 请使用 [Command.onCommand] * * ### 指令解析流程 * 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理) From 7762ea2f656053a6fb2b9b1238251ca5ad6d1aed Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 22:55:19 +0800 Subject: [PATCH 038/114] Separate Command.names into Command.primaryName and Command.secondaryNames such that primaryName is compulsory while secondaryNames are optional. --- .../mirai/console/command/BuiltInCommands.kt | 12 ++-- .../mamoe/mirai/console/command/Command.kt | 60 ++++++++++++------- .../command/CommandExecutionException.kt | 1 - .../mirai/console/command/CommandManager.kt | 4 +- .../mirai/console/command/CommandOwner.kt | 2 +- .../CommandPermissionDeniedException.kt | 1 - .../mirai/console/command/CompositeCommand.kt | 5 +- .../mamoe/mirai/console/command/RawCommand.kt | 6 +- .../mirai/console/command/SimpleCommand.kt | 5 +- .../console/command/java/JCompositeCommand.kt | 9 +-- .../mirai/console/command/java/JRawCommand.kt | 2 +- .../console/command/java/JSimpleCommand.kt | 5 +- .../MiraiConsoleImplementationBridge.kt | 5 +- .../internal/command/CommandManagerImpl.kt | 13 ++-- .../command/CompositeCommandInternal.kt | 11 ++-- .../console/internal/command/internal.kt | 1 - .../mirai/console/permission/Permission.kt | 7 ++- .../permission/PermissionIdNamespace.kt | 6 +- .../permission/PermissionImplementation.kt | 8 ++- .../console/permission/PermissionService.kt | 12 +++- .../console/plugin/jvm/AbstractJvmPlugin.kt | 2 +- .../mirai/console/terminal/ConsoleThread.kt | 1 - 22 files changed, 110 insertions(+), 68 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index 764573ccf..1b24675be 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -132,12 +132,12 @@ public object BuiltInCommands { onFailure = { throwable -> sendMessage( "Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" + - if (this is CommandSenderOnMessage<*>) { - CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) { - fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") } - } - "\n 1 分钟内发送 stacktrace 以获取堆栈信息" - } else "" + if (this is CommandSenderOnMessage<*>) { + CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) { + fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") } + } + "\n 1 分钟内发送 stacktrace 以获取堆栈信息" + } else "" ) throw throwable diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index c89117904..b984d7166 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -16,8 +16,8 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.java.JCommand import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission -import net.mamoe.mirai.console.internal.command.isValidSubName import net.mamoe.mirai.console.permission.Permission +import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.message.data.MessageChain /** @@ -33,13 +33,17 @@ import net.mamoe.mirai.message.data.MessageChain */ public interface Command { /** - * 指令名. 需要至少有一个元素. 所有元素都不能带有空格 - * - * 第一个元素会作为主指令名. + * 主指令名. 将会参与构成 [Permission.id]. * + * 不允许包含 [空格][Char.isWhitespace], '.', ':'. + */ + public val primaryName: String + + /** + * 次要指令名 * @see Command.primaryName 获取主指令名 */ - public val names: Array + public val secondaryNames: Array /** * 用法说明, 用于发送给用户. [usage] 一般包含 [description]. @@ -52,14 +56,18 @@ public interface Command { public val description: String /** - * 指令权限 + * 此指令所分配的权限. + * + * ### 实现约束 + * - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace] + * - [PermissionId.name] 应为 [主指令名][primaryName] */ public val permission: Permission /** * 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选. * - * 会影响消息语境中的解析. + * 会影响聊天语境中的解析. */ public val prefixOptional: Boolean @@ -80,17 +88,34 @@ public interface Command { public suspend fun CommandSender.onCommand(args: MessageChain) public companion object { + /** - * 主要指令名. 为 [Command.names] 的第一个元素. + * 获取所有指令名称 (包含 [primaryName] 和 [secondaryNames]). + * + * @return 数组大小至少为 1. 第一个元素总是 [primaryName]. 随后是保持原顺序的 [secondaryNames] */ @JvmStatic - public val Command.primaryName: String - get() = names[0] + public val Command.allNames: Array + get() = arrayOf(primaryName, *secondaryNames) + + /** + * 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException] + */ + @Throws(IllegalArgumentException::class) + public fun checkCommandName(name: String) { + when { + name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.") + name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in command name.") + name.contains(':') -> throw IllegalArgumentException("':' is forbidden in command name.") + name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.") + } + } } } /** * 调用 [Command.onCommand] + * @see Command.onCommand */ @JvmSynthetic public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit = @@ -105,19 +130,14 @@ public suspend inline fun Command.onCommand(sender: CommandSender, args: Message */ public abstract class AbstractCommand @JvmOverloads constructor( - /** 指令拥有者. */ - public override val owner: CommandOwner, - vararg names: String, - description: String = "", + public final override val owner: CommandOwner, + public final override val primaryName: String, + public final override val secondaryNames: Array, + public override val description: String = "", parentPermission: Permission = owner.parentPermission, /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ public override val prefixOptional: Boolean = false, ) : Command { - public override val description: String = description.trimIndent() - public final override val names: Array = - names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list -> - list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") } - }.toTypedArray() - + public override val usage: String get() = description public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt index a2cef3687..1998b71b0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecutionException.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index ba6c696a1..81e1d6132 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -53,8 +53,8 @@ public interface CommandManager { * * @param override 是否覆盖重名指令. * - * 若原有指令 P, 其 [Command.names] 为 'a', 'b', 'c'. - * 新指令 Q, 其 [Command.names] 为 'b', 将会覆盖原指令 A 注册的 'b'. + * 若原有指令 P, 其 [Command.secondaryNames] 为 'a', 'b', 'c'. + * 新指令 Q, 其 [Command.secondaryNames] 为 'b', 将会覆盖原指令 A 注册的 'b'. * * 即注册完成后, 'a' 和 'c' 将会解析到指令 P, 而 'b' 会解析到指令 Q. * diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt index 2515d05b8..f9c627edf 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt @@ -36,5 +36,5 @@ public interface CommandOwner : PermissionIdNamespace { internal object ConsoleCommandOwner : CommandOwner { override val parentPermission: Permission get() = BuiltInCommands.parentPermission - override fun permissionId(name: String): PermissionId = PermissionId("console", "command.$name") + override fun permissionId(name: String): PermissionId = PermissionId("console", name) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt index d0d976a30..f966ce96d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermissionDeniedException.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 5cd7bdb2b..27ed74c57 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -81,12 +81,13 @@ import kotlin.annotation.AnnotationTarget.FUNCTION */ public abstract class CompositeCommand( owner: CommandOwner, - vararg names: String, + primaryName: String, + vararg secondaryNames: String, description: String = "no description available", parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, -) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional), +) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), CommandArgumentContextAware { /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index 621172e97..eeabedc10 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -35,8 +35,10 @@ public abstract class RawCommand( * @see CommandOwner */ public override val owner: CommandOwner, - /** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */ - public override vararg val names: String, + /** 主指令名. */ + public override val primaryName: String, + /** 次要指令名. */ + public override vararg val secondaryNames: String, /** 用法说明, 用于发送给用户 */ public override val usage: String = "", /** 指令描述, 用于显示在 [BuiltInCommands.Help] */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 55e5ef438..2a49ebcb0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -50,12 +50,13 @@ import net.mamoe.mirai.message.data.MessageChain */ public abstract class SimpleCommand( owner: CommandOwner, - vararg names: String, + primaryName: String, + vararg secondaryNames: String, description: String = "no description available", parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, -) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional), +) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional), CommandArgumentContextAware { /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt index 6dacd29ac..fdb5aee47 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt @@ -69,11 +69,12 @@ import net.mamoe.mirai.console.permission.Permission public abstract class JCompositeCommand @JvmOverloads constructor( owner: CommandOwner, - vararg names: String, + primaryName: String, + vararg secondaryNames: String, parentPermission: Permission = owner.parentPermission, -) : CompositeCommand(owner, *names, parentPermission = parentPermission) { - /** 指令描述, 用于显示在 [BuiltInCommands.Help] */ - public final override var description: String = "" +) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) { + /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ + public final override var description: String = "" protected set public final override var permission: Permission = super.permission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt index 07b03c641..3815a7332 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt @@ -52,7 +52,7 @@ public abstract class JRawCommand */ public override val owner: CommandOwner, /** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */ - public override vararg val names: String, + public override vararg val secondaryNames: String, parentPermission: Permission = owner.parentPermission, ) : Command { /** 用法说明, 用于发送给用户 */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt index 37f7022b9..b136af3be 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt @@ -41,9 +41,10 @@ import net.mamoe.mirai.console.permission.Permission */ public abstract class JSimpleCommand( owner: CommandOwner, - vararg names: String, + primaryName: String, + vararg secondaryNames: String, basePermission: Permission, -) : SimpleCommand(owner, *names, parentPermission = basePermission) { +) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) { public override var description: String = super.description protected set diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index 09d354e12..0751b2432 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -23,7 +23,6 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.BuiltInCommands -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.PluginDataStorage @@ -191,6 +190,10 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI PluginManagerImpl.enableAllLoadedPlugins() + for (registeredCommand in CommandManager.allRegisteredCommands) { + registeredCommand.permission // init + } + mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index 6178ef36f..2fb30f9b3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.subscribeAlways @@ -101,7 +100,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine override fun Command.register(override: Boolean): Boolean { if (this is CompositeCommand) this.subCommands // init lazy this.permission // init lazy - this.names // init lazy + this.secondaryNames // init lazy this.description // init lazy this.usage // init lazy @@ -111,13 +110,13 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine } registeredCommands.add(this@register) if (this.prefixOptional) { - for (name in this.names) { + for (name in this.secondaryNames) { val lowerCaseName = name.toLowerCase() optionalPrefixCommandMap[lowerCaseName] = this requiredPrefixCommandMap[lowerCaseName] = this } } else { - for (name in this.names) { + for (name in this.secondaryNames) { val lowerCaseName = name.toLowerCase() optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency requiredPrefixCommandMap[lowerCaseName] = this @@ -128,15 +127,15 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine } override fun Command.findDuplicate(): Command? = - registeredCommands.firstOrNull { it.names intersectsIgnoringCase this.names } + registeredCommands.firstOrNull { it.secondaryNames intersectsIgnoringCase this.secondaryNames } override fun Command.unregister(): Boolean = modifyLock.withLock { if (this.prefixOptional) { - this.names.forEach { + this.secondaryNames.forEach { optionalPrefixCommandMap.remove(it) } } - this.names.forEach { + this.secondaryNames.forEach { requiredPrefixCommandMap.remove(it) } registeredCommands.remove(this) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index 0a3948277..f922229d4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.description.CommandArgumentContext import net.mamoe.mirai.console.command.description.CommandArgumentContextAware import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip @@ -42,19 +41,21 @@ internal object SimpleCommandSubCommandAnnotationResolver : function.hasAnnotation() override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array = - baseCommand.names + baseCommand.secondaryNames } internal abstract class AbstractReflectionCommand @JvmOverloads constructor( owner: CommandOwner, - names: Array, + primaryName: String, + secondaryNames: Array, description: String = "", parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, ) : Command, AbstractCommand( owner, - names = names, + primaryName = primaryName, + secondaryNames = secondaryNames, description = description, parentPermission = parentPermission, prefixOptional = prefixOptional @@ -251,7 +252,7 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm internal fun AbstractReflectionCommand.createSubCommand( function: KFunction<*>, - context: CommandArgumentContext + context: CommandArgumentContext, ): AbstractReflectionCommand.SubCommandDescriptor { val notStatic = !function.hasAnnotation() //val overridePermission = null//function.findAnnotation()//optional diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt index b57516237..ac83357ac 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt @@ -10,7 +10,6 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.Command -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.contact.Group diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt index 289b4b214..b7c7cd80f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt @@ -13,9 +13,9 @@ import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.Command /** - * 一个权限. + * 一个抽象的「权限」. 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id]. * - * 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id]. + * 在匹配权限时, 应使用唯一的 [id] 作为依据. 而不应该使用 [Permission] 实例. 同时, [Permission] 也不适合存储. * * **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例. * @@ -32,6 +32,7 @@ import net.mamoe.mirai.console.command.Command * #### 手动申请权限 * [PermissionService.register] */ +@PermissionImplementation public interface Permission { /** * 唯一识别 ID. 所有权限的 [id] 都互不相同. @@ -49,6 +50,8 @@ public interface Permission { /** * 父权限. * + * 在检查权限时, 若一个 [Permittee] 拥有父 + * * [RootPermission] 的 parent 为自身 */ public val parent: Permission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt index 6c8cb4b4c..3b4c16777 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt @@ -9,12 +9,16 @@ package net.mamoe.mirai.console.permission +import net.mamoe.mirai.console.command.Command + /** * [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace]. */ public interface PermissionIdNamespace { /** - * 创建一个此命名空间下的 [PermitteeId] + * 创建一个此命名空间下的 [PermitteeId]. + * + * 在指令初始化时, 会申请对应权限. 此时 [name] 为 "command.$primaryName` 其中 [primaryName][Command.primaryName]. */ public fun permissionId(name: String): PermissionId } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt index c215e224d..2e79f262e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt @@ -12,10 +12,12 @@ package net.mamoe.mirai.console.permission import kotlin.annotation.AnnotationTarget.* /** - * 表示一个应该由权限插件实现的类. + * 表示一个应该由专有的权限插件 (提供 [PermissionService] 的插件) 实现的类. * - * 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段. - * 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们. + * + * 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意直接或间接实现他们的手段 (否则会导致 [PermissionService] 处理异常). + * + * 普通插件仅应该使用从 [PermissionService] 或其他途径获取这些对象. */ @Retention(AnnotationRetention.BINARY) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt index ed014fa99..522fc88d1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt @@ -77,6 +77,8 @@ public interface PermissionService

{ * 申请并注册一个权限 [Permission]. * * @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出. + * + * @return 申请到的 [Permission] 实例 */ @Throws(PermissionRegistryConflictException::class) public fun register( @@ -90,16 +92,17 @@ public interface PermissionService

{ /** * 授予 [permitteeId] 以 [permission] 权限 * - * Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持. + * Console 内建的权限服务支持此操作. 但插件扩展的权限服务可能不支持. * * @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出. */ + @Throws(UnsupportedOperationException::class) public fun permit(permitteeId: PermitteeId, permission: P) /** * 撤销 [permitteeId] 的 [permission] 授权 * - * Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持. + * Console 内建的权限服务支持此操作. 但插件扩展的权限服务可能不支持. * * @param recursive `true` 时递归撤销所有子权限. * 例如, 若 [permission] 为 "*:*", @@ -108,6 +111,7 @@ public interface PermissionService

{ * * @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出. */ + @Throws(UnsupportedOperationException::class) public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) public companion object { @@ -118,6 +122,10 @@ public interface PermissionService

{ public val INSTANCE: PermissionService get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.") + /** + * 获取一个权限, 失败时抛出 [NoSuchElementException] + */ + @Throws(NoSuchElementException::class) public fun

PermissionService

.getOrFail(id: PermissionId): P = get(id) ?: throw NoSuchElementException("Permission not found: $id") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index 95dcfb1d0..9ed8caa29 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -37,7 +37,7 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor( public final override val loader: JvmPluginLoader get() = super.loader - public final override fun permissionId(name: String): PermissionId = PermissionId(description.id, "command.$name") + public final override fun permissionId(name: String): PermissionId = PermissionId(description.id, name) /** * 重载 [PluginData] diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt index a6cad4cc6..a21b63c1e 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.BuiltInCommands -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandExecuteStatus import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand From 8b5c6dfa274dd302fcbb1960b67490cb6438df49 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:03:21 +0800 Subject: [PATCH 039/114] Fix returnType and add JvmBlockingBridge for ConsoleCommandSender.sendMessage --- .../kotlin/net/mamoe/mirai/console/command/CommandSender.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 9d3b76f60..fa0aec048 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -501,7 +501,6 @@ public fun CommandSender.getBotOrNull(): Bot? { * * 控制台拥有一切指令的执行权限. */ -// 前端实现 public object ConsoleCommandSender : AbstractCommandSender() { public const val NAME: String = "ConsoleCommandSender" @@ -514,12 +513,15 @@ public object ConsoleCommandSender : AbstractCommandSender() { public override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) } + + @JvmBlockingBridge public override suspend fun sendMessage(message: Message): Nothing? { MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message) return null } - public override suspend fun sendMessage(message: String): MessageReceipt? { + @JvmBlockingBridge + public override suspend fun sendMessage(message: String): Nothing? { MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message) return null } From 5a34d58975fcbe2b929dc486a81ecfb465a78796 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:15:29 +0800 Subject: [PATCH 040/114] Check command names on init --- .../main/kotlin/net/mamoe/mirai/console/command/Command.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index b984d7166..1c308c530 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -138,6 +138,11 @@ public abstract class AbstractCommand /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ public override val prefixOptional: Boolean = false, ) : Command { + init { + Command.checkCommandName(primaryName) + secondaryNames.forEach(Command::checkCommandName) + } + public override val usage: String get() = description public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } } \ No newline at end of file From f40dd189f205ca0c5170e127a17e3cee05765124 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:26:16 +0800 Subject: [PATCH 041/114] Fix commands --- .../mirai/console/command/CommandManager.kt | 2 +- .../mirai/console/command/java/JCommand.kt | 5 ++- .../console/command/java/JCompositeCommand.kt | 2 +- .../console/command/java/JSimpleCommand.kt | 1 + .../internal/command/CommandManagerImpl.kt | 36 +++++++++++-------- .../mirai/console/command/TestCommand.kt | 2 +- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 81e1d6132..b2c058d78 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -157,7 +157,7 @@ public interface CommandManager { public companion object INSTANCE : CommandManager by CommandManagerImpl { // TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191 - override val CommandOwner.registeredCommands: List get() = CommandManagerImpl.run { registeredCommands } + override val CommandOwner.registeredCommands: List get() = CommandManagerImpl.run { this@registeredCommands.registeredCommands } override fun CommandOwner.unregisterAllCommands(): Unit = CommandManagerImpl.run { unregisterAllCommands() } override fun Command.register(override: Boolean): Boolean = CommandManagerImpl.run { register(override) } override fun Command.findDuplicate(): Command? = CommandManagerImpl.run { findDuplicate() } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt index 38d3d2c0f..1fbf748c0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCommand.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.SingleMessage /** * 为 Java 用户添加协程帮助的 [Command]. @@ -33,9 +32,9 @@ public interface JCommand : Command { /** * 在指令被执行时调用. * - * @param args 指令参数. 数组元素类型可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割. + * @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数. * * @see CommandManager.executeCommand 查看更多信息 */ - public fun onCommand(sender: CommandSender, args: MessageChain) // overrides bridge + public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt index fdb5aee47..09bc507e6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt @@ -24,7 +24,7 @@ import net.mamoe.mirai.console.permission.Permission * public final class MyCompositeCommand extends CompositeCommand { * public static final MyCompositeCommand INSTANCE = new MyCompositeCommand(); * - * public MyCompositeCommand() { + * private MyCompositeCommand() { * super(MyPluginMain.INSTANCE, "manage") // "manage" 是主指令名 * } * diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt index b136af3be..e7a3eb3fd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.console.permission.Permission * Java 实现: * ```java * public final class MySimpleCommand extends JSimpleCommand { + * public static final MySimpleCommand INSTANCE = new MySimpleCommand(); * private MySimpleCommand() { * super(MyPlugin.INSTANCE, "tell") * // 可选设置如下属性 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index 2fb30f9b3..eb34fb34a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* +import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.subscribeAlways @@ -30,8 +31,9 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine MiraiConsole.createLogger("command") } + @Suppress("ObjectPropertyName") @JvmField - internal val registeredCommands: MutableList = mutableListOf() + internal val _registeredCommands: MutableList = mutableListOf() @JvmField internal val requiredPrefixCommandMap: MutableMap = mutableMapOf() @@ -88,8 +90,8 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine ///// IMPL - override val CommandOwner.registeredCommands: List get() = CommandManagerImpl.registeredCommands.filter { it.owner == this } - override val allRegisteredCommands: List get() = registeredCommands.toList() // copy + override val CommandOwner.registeredCommands: List get() = _registeredCommands.filter { it.owner == this } + override val allRegisteredCommands: List get() = _registeredCommands.toList() // copy override val commandPrefix: String get() = "/" override fun CommandOwner.unregisterAllCommands() { for (registeredCommand in registeredCommands) { @@ -99,24 +101,28 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine override fun Command.register(override: Boolean): Boolean { if (this is CompositeCommand) this.subCommands // init lazy - this.permission // init lazy - this.secondaryNames // init lazy - this.description // init lazy - this.usage // init lazy + kotlin.runCatching { + this.permission // init lazy + this.secondaryNames // init lazy + this.description // init lazy + this.usage // init lazy + }.onFailure { + throw IllegalStateException("Failed to init command ${this@register}.", it) + } modifyLock.withLock { if (!override) { if (findDuplicate() != null) return false } - registeredCommands.add(this@register) + _registeredCommands.add(this@register) if (this.prefixOptional) { - for (name in this.secondaryNames) { + for (name in this.allNames) { val lowerCaseName = name.toLowerCase() optionalPrefixCommandMap[lowerCaseName] = this requiredPrefixCommandMap[lowerCaseName] = this } } else { - for (name in this.secondaryNames) { + for (name in this.allNames) { val lowerCaseName = name.toLowerCase() optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency requiredPrefixCommandMap[lowerCaseName] = this @@ -127,21 +133,21 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine } override fun Command.findDuplicate(): Command? = - registeredCommands.firstOrNull { it.secondaryNames intersectsIgnoringCase this.secondaryNames } + _registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase this.allNames } override fun Command.unregister(): Boolean = modifyLock.withLock { if (this.prefixOptional) { - this.secondaryNames.forEach { + this.allNames.forEach { optionalPrefixCommandMap.remove(it) } } - this.secondaryNames.forEach { + this.allNames.forEach { requiredPrefixCommandMap.remove(it) } - registeredCommands.remove(this) + _registeredCommands.remove(this) } - override fun Command.isRegistered(): Boolean = this in registeredCommands + override fun Command.isRegistered(): Boolean = this in _registeredCommands override suspend fun Command.execute( sender: CommandSender, diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 3db1b0a52..122475bed 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -79,7 +79,7 @@ internal class TestCommand { assertEquals(1, ConsoleCommandOwner.registeredCommands.size) - assertEquals(1, CommandManagerImpl.registeredCommands.size) + assertEquals(1, CommandManagerImpl._registeredCommands.size) assertEquals(2, CommandManagerImpl.requiredPrefixCommandMap.size) } finally { TestCompositeCommand.unregister() From ca40a292cfaeaa3bcd0ff4d1c9ccf1c7fe5e1b66 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:30:40 +0800 Subject: [PATCH 042/114] Add serializersModule --- .../mamoe/mirai/console/data/AbstractPluginData.kt | 7 ++++++- .../kotlin/net/mamoe/mirai/console/data/PluginData.kt | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt index 72ca966b2..90b22bd06 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt @@ -7,11 +7,13 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE") +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.console.internal.data.PluginDataImpl import net.mamoe.mirai.console.internal.data.getAnnotationListForValueSerialization import net.mamoe.mirai.console.internal.data.valueName @@ -72,6 +74,9 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { public final override val updaterSerializer: KSerializer get() = super.updaterSerializer + @ConsoleExperimentalApi + public override val serializersModule: SerializersModule = EmptySerializersModule + /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 119b9f2e3..c8c01736d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -18,6 +18,8 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.serializersModuleOf import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR import net.mamoe.mirai.console.data.java.JAutoSavePluginData @@ -124,6 +126,15 @@ public interface PluginData { @ConsoleExperimentalApi public fun onValueChanged(value: Value<*>) + /** + * 用于支持多态序列化. + * + * @see SerializersModule + * @see serializersModuleOf + */ + @ConsoleExperimentalApi + public val serializersModule: SerializersModule + /** * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 */ From 16598ee8ec81e7a83e14d5908bf7fff2e0fb4636 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:33:08 +0800 Subject: [PATCH 043/114] Fix docs --- .../main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt | 2 +- .../kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index eeabedc10..3e967be97 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -41,7 +41,7 @@ public abstract class RawCommand( public override vararg val secondaryNames: String, /** 用法说明, 用于发送给用户 */ public override val usage: String = "", - /** 指令描述, 用于显示在 [BuiltInCommands.Help] */ + /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public override val description: String = "", /** 指令父权限 */ parentPermission: Permission = owner.parentPermission, diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt index 3815a7332..8f773cd51 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt @@ -59,7 +59,7 @@ public abstract class JRawCommand public override var usage: String = "" protected set - /** 指令描述, 用于显示在 [BuiltInCommands.Help] */ + /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public final override var description: String = "" protected set From c1f1b6954a7491362ad5833bd05b47182e7ede33 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:39:34 +0800 Subject: [PATCH 044/114] Rename PluginDescriptionChecker.kt to ContextualParametersChecker.kt for further extensibility --- .../intellij/IDEContainerContributor.kt | 4 +- ...cker.kt => ContextualParametersChecker.kt} | 41 +------------------ 2 files changed, 4 insertions(+), 41 deletions(-) rename tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/{PluginDescriptionChecker.kt => ContextualParametersChecker.kt} (74%) diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt index 24cc9ef81..772e5acb4 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/IDEContainerContributor.kt @@ -9,8 +9,8 @@ package net.mamoe.mirai.console.intellij +import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker -import net.mamoe.mirai.console.intellij.diagnostics.PluginDescriptionChecker import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useInstance import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -22,7 +22,7 @@ class IDEContainerContributor : StorageComponentContainerContributor { platform: org.jetbrains.kotlin.platform.TargetPlatform, moduleDescriptor: ModuleDescriptor, ) { - container.useInstance(PluginDescriptionChecker()) + container.useInstance(ContextualParametersChecker()) container.useInstance(PluginDataValuesChecker()) } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt similarity index 74% rename from tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt rename to tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt index b3f0b3d1c..1a62b1f5f 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDescriptionChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt @@ -24,11 +24,9 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.util.* /** - * Checks: - * - plugin id - * - plugin name + * Checks paramters with [ResolveContextKind] */ -class PluginDescriptionChecker : DeclarationChecker { +class ContextualParametersChecker : DeclarationChecker { companion object { 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 = arrayOf("main", "console", "plugin", "config", "data") @@ -103,40 +101,5 @@ class PluginDescriptionChecker : DeclarationChecker { fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } } return - /* - when (declaration) { - is KtClassOrObject -> { - // check super type constructor - val superTypeCallEntry = declaration.findChild()?.findChild() ?: return - // val constructorCall = superTypeCallEntry.findChildren()?.resolveToCall() ?: return - val valueArgumentList = superTypeCallEntry.findChild() ?: return - valueArgumentList.arguments.asSequence().mapNotNull(KtValueArgument::getArgumentExpression).forEach { - for (child in it.allChildrenWithSelf) { - if (child is LambdaArgument) { - child.getLambdaExpression()?.bodyExpression?.statements?.forEach { statement -> - if (statement is KtCallExpression) check(statement, context) - } - } - if (child is KtCallExpression) { - check(child, context) - } - } - } - } - else -> { - declaration.children.flatMap { - when (it) { - is KtCallExpression -> listOf(it) - is KtLambdaExpression -> it.bodyExpression?.statements.orEmpty() - else -> emptyList() - } - }.forEach { element -> - if (element is KtDeclaration) { - val desc = element.descriptor ?: return@forEach - check(element, desc, context) - } - } - } - }*/ } } \ No newline at end of file From b0e082000c0cf9aa8604332df8466777c1430c0d Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:50:02 +0800 Subject: [PATCH 045/114] Introduce ResolveContext for permission and command --- .../kotlin/net/mamoe/mirai/console/command/Command.kt | 6 +++++- .../net/mamoe/mirai/console/command/CommandOwner.kt | 6 +++++- .../mamoe/mirai/console/command/CompositeCommand.kt | 10 +++++++--- .../net/mamoe/mirai/console/command/RawCommand.kt | 5 +++-- .../net/mamoe/mirai/console/command/SimpleCommand.kt | 6 ++++-- .../mirai/console/command/java/JCompositeCommand.kt | 6 ++++-- .../mamoe/mirai/console/command/java/JRawCommand.kt | 6 ++++-- .../mamoe/mirai/console/command/java/JSimpleCommand.kt | 6 ++++-- .../mirai/console/compiler/common/ResolveContext.kt | 5 +++++ .../net/mamoe/mirai/console/permission/PermissionId.kt | 7 +++++-- .../mirai/console/permission/PermissionIdNamespace.kt | 4 +++- .../mirai/console/permission/PermissionService.kt | 6 ++++-- .../net/mamoe/mirai/console/permission/PermitteeId.kt | 2 +- 13 files changed, 54 insertions(+), 21 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 1c308c530..9d39cfa7e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -15,6 +15,8 @@ import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.java.JCommand +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId @@ -37,12 +39,14 @@ public interface Command { * * 不允许包含 [空格][Char.isWhitespace], '.', ':'. */ + @ResolveContext(COMMAND_NAME) public val primaryName: String /** * 次要指令名 * @see Command.primaryName 获取主指令名 */ + @ResolveContext(COMMAND_NAME) public val secondaryNames: Array /** @@ -102,7 +106,7 @@ public interface Command { * 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException] */ @Throws(IllegalArgumentException::class) - public fun checkCommandName(name: String) { + public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) { when { name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.") name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in command name.") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt index f9c627edf..f59033c95 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandOwner.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionIdNamespace @@ -36,5 +38,7 @@ public interface CommandOwner : PermissionIdNamespace { internal object ConsoleCommandOwner : CommandOwner { override val parentPermission: Permission get() = BuiltInCommands.parentPermission - override fun permissionId(name: String): PermissionId = PermissionId("console", name) + override fun permissionId( + @ResolveContext(PERMISSION_NAME) name: String, + ): PermissionId = PermissionId("console", name) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 27ed74c57..21fbd2239 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -18,6 +18,8 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.description.* +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission @@ -81,8 +83,8 @@ import kotlin.annotation.AnnotationTarget.FUNCTION */ public abstract class CompositeCommand( owner: CommandOwner, - primaryName: String, - vararg secondaryNames: String, + @ResolveContext(COMMAND_NAME) primaryName: String, + @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, description: String = "no description available", parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, @@ -106,7 +108,9 @@ public abstract class CompositeCommand( */ @Retention(RUNTIME) @Target(FUNCTION) - protected annotation class SubCommand(vararg val value: String) + protected annotation class SubCommand( + @ResolveContext(COMMAND_NAME) vararg val value: String, + ) /** 指令描述 */ @Retention(RUNTIME) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index 3e967be97..6133e5da2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.java.JRawCommand +import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain @@ -36,9 +37,9 @@ public abstract class RawCommand( */ public override val owner: CommandOwner, /** 主指令名. */ - public override val primaryName: String, + @ResolveContext(ResolveContext.Kind.COMMAND_NAME) public override val primaryName: String, /** 次要指令名. */ - public override vararg val secondaryNames: String, + @ResolveContext(ResolveContext.Kind.COMMAND_NAME) public override vararg val secondaryNames: String, /** 用法说明, 用于发送给用户 */ public override val usage: String = "", /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 2a49ebcb0..27bb0f6e3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -20,6 +20,8 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.description.* import net.mamoe.mirai.console.command.java.JSimpleCommand +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission @@ -50,8 +52,8 @@ import net.mamoe.mirai.message.data.MessageChain */ public abstract class SimpleCommand( owner: CommandOwner, - primaryName: String, - vararg secondaryNames: String, + @ResolveContext(COMMAND_NAME) primaryName: String, + @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, description: String = "no description available", parentPermission: Permission = owner.parentPermission, prefixOptional: Boolean = false, diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt index 09bc507e6..d09d2275c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JCompositeCommand.kt @@ -14,6 +14,8 @@ import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.description.buildCommandArgumentContext +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission /** @@ -69,8 +71,8 @@ import net.mamoe.mirai.console.permission.Permission public abstract class JCompositeCommand @JvmOverloads constructor( owner: CommandOwner, - primaryName: String, - vararg secondaryNames: String, + @ResolveContext(COMMAND_NAME) primaryName: String, + @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, parentPermission: Permission = owner.parentPermission, ) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) { /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt index 8f773cd51..84b06ea5c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JRawCommand.kt @@ -13,6 +13,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain @@ -51,8 +53,8 @@ public abstract class JRawCommand * @see CommandOwner */ public override val owner: CommandOwner, - /** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */ - public override vararg val secondaryNames: String, + @ResolveContext(COMMAND_NAME) public override val primaryName: String, + @ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, parentPermission: Permission = owner.parentPermission, ) : Command { /** 用法说明, 用于发送给用户 */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt index e7a3eb3fd..98e650ebd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/java/JSimpleCommand.kt @@ -14,6 +14,8 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.description.CommandArgumentContext +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.permission.Permission /** @@ -42,8 +44,8 @@ import net.mamoe.mirai.console.permission.Permission */ public abstract class JSimpleCommand( owner: CommandOwner, - primaryName: String, - vararg secondaryNames: String, + @ResolveContext(COMMAND_NAME) primaryName: String, + @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, basePermission: Permission, ) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) { public override var description: String = super.description diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index 61e481370..714c40f9e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -36,6 +36,11 @@ public annotation class ResolveContext( PLUGIN_NAME, PLUGIN_VERSION, + COMMAND_NAME, + + PERMISSION_NAMESPACE, + PERMISSION_NAME, + /** * Custom serializers allowed */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt index 2d527d1e2..abac4bf12 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt @@ -12,6 +12,9 @@ package net.mamoe.mirai.console.permission import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAMESPACE import net.mamoe.mirai.console.internal.data.map @@ -25,8 +28,8 @@ import net.mamoe.mirai.console.internal.data.map */ @Serializable(with = PermissionId.PermissionIdAsStringSerializer::class) public data class PermissionId( - public val namespace: String, - public val name: String, + @ResolveContext(PERMISSION_NAMESPACE) public val namespace: String, + @ResolveContext(PERMISSION_NAME) public val name: String, ) { init { require(!namespace.contains(':')) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt index 3b4c16777..90a043608 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt @@ -10,6 +10,8 @@ package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.command.Command +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME /** * [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace]. @@ -20,5 +22,5 @@ public interface PermissionIdNamespace { * * 在指令初始化时, 会申请对应权限. 此时 [name] 为 "command.$primaryName` 其中 [primaryName][Command.primaryName]. */ - public fun permissionId(name: String): PermissionId + public fun permissionId(@ResolveContext(PERMISSION_NAME) name: String): PermissionId } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt index 522fc88d1..88c55556d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt @@ -11,6 +11,8 @@ package net.mamoe.mirai.console.permission +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.internal.permission.checkType import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf @@ -129,8 +131,8 @@ public interface PermissionService

{ public fun

PermissionService

.getOrFail(id: PermissionId): P = get(id) ?: throw NoSuchElementException("Permission not found: $id") - internal fun PermissionService<*>.allocatePermissionIdForPlugin(name: String, id: String) = - PermissionId("plugin.${name.toLowerCase()}", id.toLowerCase()) + internal fun PermissionService<*>.allocatePermissionIdForPlugin(pluginName: String, @ResolveContext(COMMAND_NAME) permissionName: String) = + PermissionId("plugin.${pluginName.toLowerCase()}", permissionName.toLowerCase()) public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this] diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt index dc73b4d52..ec46d5da7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt @@ -169,7 +169,7 @@ public interface PermitteeId { * ExactTemp * ``` */ -@Serializable(with = AbstractPermitteeId.AsStringSerializer::class) +@Serializable(with = AsStringSerializer::class) public sealed class AbstractPermitteeId( public final override vararg val directParents: PermitteeId, ) : PermitteeId { From 4c0daefd671f216ac1cf3cd0ef6d2f87abbb7ee7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:50:57 +0800 Subject: [PATCH 046/114] Replace with static import --- .../kotlin/net/mamoe/mirai/console/command/RawCommand.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index 6133e5da2..a924e6982 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -15,6 +15,7 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.MessageChain @@ -37,9 +38,9 @@ public abstract class RawCommand( */ public override val owner: CommandOwner, /** 主指令名. */ - @ResolveContext(ResolveContext.Kind.COMMAND_NAME) public override val primaryName: String, + @ResolveContext(COMMAND_NAME) public override val primaryName: String, /** 次要指令名. */ - @ResolveContext(ResolveContext.Kind.COMMAND_NAME) public override vararg val secondaryNames: String, + @ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, /** 用法说明, 用于发送给用户 */ public override val usage: String = "", /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ From 56a70986275846c1bc314c81b398ecfd2822dbd7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 18 Sep 2020 23:58:01 +0800 Subject: [PATCH 047/114] Support checking ArrayValue compile-time constants --- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../ContextualParametersChecker.kt | 10 +++--- .../console/intellij/resolve/resolveIdea.kt | 31 +++++++++++++------ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 468108008..322a3d368 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-3" + const val console = "1.0-RC-dev-4" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt index 1a62b1f5f..fc5330cc5 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt @@ -14,7 +14,7 @@ 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.resolveAllCalls -import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValue +import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic @@ -93,12 +93,14 @@ class ContextualParametersChecker : DeclarationChecker { p.resolveContextKind?.let(checkersMap::get)?.let { it to a } } .mapNotNull { (kind, argument) -> - argument.resolveStringConstantValue(context.bindingContext)?.let { const -> + argument.resolveStringConstantValues()?.let { const -> Triple(kind, argument, const) } } - .forEach { (fn, argument, resolvedConstant) -> - fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } + .forEach { (fn, argument, resolvedConstants) -> + for (resolvedConstant in resolvedConstants) { + fn(argument.asElement(), resolvedConstant)?.let { context.report(it) } + } } return } diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index 4d3614456..a03640687 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -30,6 +30,8 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.constants.ArrayValue +import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance @@ -106,8 +108,8 @@ fun ResolvedCall<*>.valueParametersWithArguments(): List? { + return this.getArgumentExpression()?.resolveStringConstantValues() } val PsiElement.allChildrenFlat: Sequence @@ -131,14 +133,24 @@ fun KtElement?.getResolvedCallOrResolveToCall( val ResolvedCall.valueParameters: List get() = this.resultingDescriptor.valueParameters -fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): String? { +fun ConstantValue<*>.selfOrChildrenConstantStrings(): Sequence { + return when (this) { + is StringValue -> sequenceOf(value) + is ArrayValue -> sequence { + yieldAll(this@selfOrChildrenConstantStrings.selfOrChildrenConstantStrings()) + } + else -> emptySequence() + } +} + +fun KtExpression.resolveStringConstantValues(): Sequence { when (this) { is KtNameReferenceExpression -> { when (val reference = references.firstIsInstance().resolve()) { is KtDeclaration -> { - val descriptor = reference.descriptor.castOrNull() ?: return null - val compileTimeConstant = descriptor.compileTimeInitializer ?: return null - return compileTimeConstant.castOrNull()?.value + val descriptor = reference.descriptor.castOrNull() ?: return emptySequence() + val compileTimeConstant = descriptor.compileTimeInitializer ?: return emptySequence() + return compileTimeConstant.selfOrChildrenConstantStrings() } is PsiDeclarationStatement -> { // TODO: 2020/9/18 compile-time constants from Java @@ -146,8 +158,8 @@ fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): Str } } is KtStringTemplateExpression -> { - if (hasInterpolation()) return null - return entries.joinToString("") { it.text } + if (hasInterpolation()) return emptySequence() + return sequenceOf(entries.joinToString("") { it.text }) } /* is KtCallExpression -> { @@ -161,7 +173,6 @@ fun KtExpression.resolveStringConstantValue(bindingContext: BindingContext): Str is KtConstantExpression -> { // TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression } - else -> return null } - return null + return emptySequence() } \ No newline at end of file From 78ebdf038de02895cdd4d444a3112126285bb14e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 00:02:36 +0800 Subject: [PATCH 048/114] Extract AbstractCommand --- .../mirai/console/command/AbstractCommand.kt | 39 +++++++++++++++++++ .../mamoe/mirai/console/command/Command.kt | 26 ------------- 2 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/AbstractCommand.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/AbstractCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/AbstractCommand.kt new file mode 100644 index 000000000..7de3bd955 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/AbstractCommand.kt @@ -0,0 +1,39 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.command + +import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission +import net.mamoe.mirai.console.permission.Permission + +/** + * [Command] 的基础实现 + * + * @see SimpleCommand + * @see CompositeCommand + * @see RawCommand + */ +public abstract class AbstractCommand +@JvmOverloads constructor( + public final override val owner: CommandOwner, + public final override val primaryName: String, + public final override val secondaryNames: Array, + public override val description: String = "", + parentPermission: Permission = owner.parentPermission, + /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ + public override val prefixOptional: Boolean = false, +) : Command { + init { + Command.checkCommandName(primaryName) + secondaryNames.forEach(Command.Companion::checkCommandName) + } + + public override val usage: String get() = description + public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 9d39cfa7e..37e4f3f69 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -17,7 +17,6 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.java.JCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME -import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.message.data.MessageChain @@ -125,28 +124,3 @@ public interface Command { public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit = sender.onCommand(args) -/** - * [Command] 的基础实现 - * - * @see SimpleCommand - * @see CompositeCommand - * @see RawCommand - */ -public abstract class AbstractCommand -@JvmOverloads constructor( - public final override val owner: CommandOwner, - public final override val primaryName: String, - public final override val secondaryNames: Array, - public override val description: String = "", - parentPermission: Permission = owner.parentPermission, - /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ - public override val prefixOptional: Boolean = false, -) : Command { - init { - Command.checkCommandName(primaryName) - secondaryNames.forEach(Command::checkCommandName) - } - - public override val usage: String get() = description - public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } -} \ No newline at end of file From 455030e6f2b5ee4be750758a54767b76c0449657 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 19 Sep 2020 00:14:27 +0800 Subject: [PATCH 049/114] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a52353cb1..ad76bb581 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. - `mirai-console-terminal`: console 的 Unix 终端界面前端. - `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. (开发中) +**注意:`mirai-console` 1.0-RC 发布之前, 前端请使用 `mirai-console-pure` 而不是 `mirai-console-terminal`** **注意:`mirai-console` 后端和 terminal 前端正在进行完全的重构, 所有 API 都不具有稳定性** From 3fa7c9e128d70927d1b4e782844e04bc3f477477 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 00:22:38 +0800 Subject: [PATCH 050/114] Support ILLEGAL_COMMAND_NAME, ILLEGAL_PERMISSION_NAME, ILLEGAL_PERMISSION_ID, ILLEGAL_PERMISSION_NAMESPACE --- .../mamoe/mirai/console/command/Command.kt | 1 + .../console/compiler/common/ResolveContext.kt | 7 ++- .../mirai/console/permission/PermissionId.kt | 31 +++++++++- .../diagnostics/MiraiConsoleErrors.java | 5 ++ .../MiraiConsoleErrorsRendering.kt | 38 ++++++++++-- .../common/resolve/MiraiConsoleTypes.kt | 8 ++- .../projects/test-project/build.gradle.kts | 2 +- .../org/example/myplugin/MyPluginMain.kt | 7 +++ .../ContextualParametersChecker.kt | 59 ++++++++++++++++--- 9 files changed, 138 insertions(+), 20 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 37e4f3f69..43346dcb9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -104,6 +104,7 @@ public interface Command { /** * 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException] */ + @JvmStatic @Throws(IllegalArgumentException::class) public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) { when { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index 714c40f9e..8a3702b69 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -32,6 +32,10 @@ public annotation class ResolveContext( * 元素数量可能在任意时间被改动 */ public enum class Kind { + /////////////////////////////////////////////////////////////////////////// + // ConstantKind + /////////////////////////////////////////////////////////////////////////// + PLUGIN_ID, PLUGIN_NAME, PLUGIN_VERSION, @@ -40,10 +44,11 @@ public annotation class ResolveContext( PERMISSION_NAMESPACE, PERMISSION_NAME, + PERMISSION_ID, // for parseFromString /** * Custom serializers allowed */ - RESTRICTED_NO_ARG_CONSTRUCTOR + RESTRICTED_NO_ARG_CONSTRUCTOR, } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt index abac4bf12..86fb7e3c3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt @@ -13,8 +13,7 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext -import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME -import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAMESPACE +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.internal.data.map @@ -57,13 +56,39 @@ public data class PermissionId( * @throws IllegalArgumentException 在解析失败时抛出. */ @JvmStatic - public fun parseFromString(string: String): PermissionId { + public fun parseFromString(@ResolveContext(PERMISSION_ID) string: String): PermissionId { return kotlin.runCatching { string.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }.getOrElse { throw IllegalArgumentException("Could not parse PermissionId from '$string'", it) } } + + /** + * 检查 [PermissionId.name] 的合法性. 在非法时抛出 [IllegalArgumentException] + */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) value: String) { + when { + value.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.") + value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.name.") + value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.") + } + } + + /** + * 检查 [PermissionId.namespace] 的合法性. 在非法时抛出 [IllegalArgumentException] + */ + @JvmStatic + @Throws(IllegalArgumentException::class) + public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) value: String) { + when { + value.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.") + value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.namespace.") + value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.") + } + } } } diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index fcf17ee32..325bd8263 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -11,6 +11,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics; import com.intellij.psi.PsiElement; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.Errors; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; @@ -19,6 +20,10 @@ public interface MiraiConsoleErrors { DiagnosticFactory1 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 UNSERIALIZABLE_TYPE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory2 ILLEGAL_COMMAND_NAME = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_NAME = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_ID = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_NAMESPACE = DiagnosticFactory2.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index ddab8060e..6140aed62 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -19,19 +19,47 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { put( ILLEGAL_PLUGIN_DESCRIPTION, "{0}", - Renderers.STRING + Renderers.STRING, ) put( NOT_CONSTRUCTABLE_TYPE, - "类型 {0} 无法通过反射直接构造, 需要提供默认值.", - Renderers.STRING + "类型 ''{0}'' 无法通过反射直接构造, 需要提供默认值.", + Renderers.STRING, ) put( UNSERIALIZABLE_TYPE, - "类型 {0} 无法被自动序列化, 需要添加序列化器", - Renderers.STRING + "类型 ''{0}'' 无法被自动序列化, 需要添加序列化器", + Renderers.STRING, + ) + + put( + ILLEGAL_COMMAND_NAME, + "指令名 ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, + ) + + put( + ILLEGAL_PERMISSION_NAME, + "权限名 ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, + ) + + put( + ILLEGAL_PERMISSION_ID, + "权限 Id ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, + ) + + put( + ILLEGAL_PERMISSION_NAMESPACE, + "权限命名空间 ''{0}'' 无效: {1}", + Renderers.STRING, + Renderers.STRING, ) } diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt index 18ae85598..478656c70 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt @@ -50,11 +50,17 @@ enum class ResolveContextKind { PLUGIN_NAME, PLUGIN_VERSION, + COMMAND_NAME, + + PERMISSION_NAMESPACE, + PERMISSION_NAME, + PERMISSION_ID, + RESTRICTED_NO_ARG_CONSTRUCTOR ; companion object { - fun valueOfOrNull(string: String): ResolveContextKind? = ResolveContextKind.values().find { it.name == string } + fun valueOfOrNull(string: String): ResolveContextKind? = values().find { it.name == string } } } diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index 4e4358b16..72f695b95 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { compileOnly(kotlin("stdlib-jdk8")) val core = "1.3.0" - val console = "1.0-RC-dev-3" + val console = "1.0-RC-dev-4" compileOnly("net.mamoe:mirai-console:$console") compileOnly("net.mamoe:mirai-core:$core") diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index 7dd80641a..a8f64c0fe 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -3,6 +3,8 @@ package org.example.myplugin import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value +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 @@ -17,6 +19,11 @@ object MyPluginMain : KotlinPlugin( id("") } ) { + override fun onEnable() { + super.onEnable() + PermissionService.INSTANCE.register(permissionId("dvs"), "ok") + } + fun test() { } diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt index fc5330cc5..790ba9596 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/ContextualParametersChecker.kt @@ -10,7 +10,7 @@ package net.mamoe.mirai.console.intellij.diagnostics 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.intellij.resolve.resolveAllCalls @@ -31,7 +31,7 @@ class ContextualParametersChecker : 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 = arrayOf("main", "console", "plugin", "config", "data") - private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名. """ + private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名""" /** * https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string @@ -40,37 +40,74 @@ class ContextualParametersChecker : DeclarationChecker { 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? { - 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.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") val lowercaseId = value.toLowerCase() if (ID_REGEX.matchEntire(value) == null) { - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 无效. 正确的插件 Id 应该满足正则表达式 '${ID_REGEX.pattern}', \n$syntax") + return 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, "'$illegal' 不允许作为插件 Id. 确保插件 Id 不完全是这个名称.") + return 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, "插件名不能为空.") + if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空") val lowercaseName = value.toLowerCase() FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称.") + return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称") } return null } fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? { if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { - return MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") + return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/") } return null } + + fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格") + value.contains(':') -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不允许包含 ':'") + value.contains('.') -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不允许包含 '.'") + else -> null + } + } + + fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "暂时不允许权限命名空间中存在空格") + value.contains(':') -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不允许包含 ':'") + else -> null + } + } + + fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? { + return when { + value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空") + value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "暂时不允许权限名称中存在空格") + value.contains(':') -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不允许包含 ':'") + else -> null + } + } + + fun checkPermissionId(inspectionTarget: PsiElement, 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 中存在空格") + value.count { it == ':' } != 1 -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 必须为 \"命名空间:名称\". 且命名空间和名称均不能包含 ':'") + else -> null + } + } } private val checkersMap: EnumMap Diagnostic?> = @@ -78,6 +115,10 @@ class ContextualParametersChecker : DeclarationChecker { put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.PLUGIN_VERSION, ::checkPluginVersion) + put(ResolveContextKind.COMMAND_NAME, ::checkCommandName) + put(ResolveContextKind.PERMISSION_NAME, ::checkPermissionName) + put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace) + put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId) } override fun check( From 38f748ba6d89b76986fed592758b0033b81c7d1a Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 19 Sep 2020 00:29:07 +0800 Subject: [PATCH 051/114] Update Run.md --- docs/Run.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Run.md b/docs/Run.md index 91a8c0fb5..2cf39c5de 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -55,7 +55,14 @@ java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader 然后就可以开始使用 mirai-console 了 -### mirai-console-terminal 前端参数 +#### mirai-console-terminal 前端参数 使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数 [mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow + + +### 启动 mirai-console-pure 前端 + +与启动 `mirai-console-terminal` 前端大体相同 +- 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure` +- 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader` From f76226f13c527b96e80d80f1157df129230aa30e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 01:06:45 +0800 Subject: [PATCH 052/114] Add QuickFix AddSerializerFix for UNSERIALIZABLE_TYPE --- .../diagnostics/MiraiConsoleErrors.java | 3 +- .../MiraiConsoleErrorsRendering.kt | 2 +- .../{MiraiConsoleTypes.kt => resolveTypes.kt} | 7 +++ .../console/intellij/QuickFixRegistrar.kt | 23 ++++++++++ .../diagnostics/PluginDataValuesChecker.kt | 4 +- .../diagnostics/fix/AddSerializerFix.kt | 44 +++++++++++++++++++ .../src/main/resources/META-INF/plugin.xml | 2 +- 7 files changed, 80 insertions(+), 5 deletions(-) rename tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/{MiraiConsoleTypes.kt => resolveTypes.kt} (92%) create mode 100644 tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/QuickFixRegistrar.kt create mode 100644 tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AddSerializerFix.kt diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index 325bd8263..e4e2e21da 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -10,6 +10,7 @@ package net.mamoe.mirai.console.compiler.common.diagnostics; import com.intellij.psi.PsiElement; +import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.Errors; @@ -19,7 +20,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; public interface MiraiConsoleErrors { DiagnosticFactory1 ILLEGAL_PLUGIN_DESCRIPTION = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 NOT_CONSTRUCTABLE_TYPE = DiagnosticFactory1.create(ERROR); - DiagnosticFactory1 UNSERIALIZABLE_TYPE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 UNSERIALIZABLE_TYPE = DiagnosticFactory1.create(ERROR); DiagnosticFactory2 ILLEGAL_COMMAND_NAME = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 ILLEGAL_PERMISSION_NAME = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 ILLEGAL_PERMISSION_ID = DiagnosticFactory2.create(ERROR); diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index 6140aed62..e3e7b7b6a 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -31,7 +31,7 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { put( UNSERIALIZABLE_TYPE, "类型 ''{0}'' 无法被自动序列化, 需要添加序列化器", - Renderers.STRING, + Renderers.FQ_NAMES_IN_TYPES, ) put( diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt similarity index 92% rename from tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt rename to tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt index 478656c70..463321b14 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/MiraiConsoleTypes.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/resolve/resolveTypes.kt @@ -15,6 +15,13 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.constants.EnumValue +/////////////////////////////////////////////////////////////////////////// +// Serializer +/////////////////////////////////////////////////////////////////////////// + +val SERIALIZABLE_FQ_NAME = FqName("kotlinx.serialization.Serializable") + + /////////////////////////////////////////////////////////////////////////// // Command /////////////////////////////////////////////////////////////////////////// diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/QuickFixRegistrar.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/QuickFixRegistrar.kt new file mode 100644 index 000000000..6e338736c --- /dev/null +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/QuickFixRegistrar.kt @@ -0,0 +1,23 @@ +package net.mamoe.mirai.console.intellij + +import com.intellij.codeInsight.intention.IntentionAction +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors +import net.mamoe.mirai.console.intellij.diagnostics.fix.AddSerializerFix +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory +import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor +import org.jetbrains.kotlin.idea.quickfix.QuickFixes + +class QuickFixRegistrar : QuickFixContributor { + override fun registerQuickFixes(quickFixes: QuickFixes) { + fun DiagnosticFactory<*>.registerFactory(vararg factory: KotlinIntentionActionsFactory) { + quickFixes.register(this, *factory) + } + + fun DiagnosticFactory<*>.registerActions(vararg action: IntentionAction) { + quickFixes.register(this, *action) + } + + MiraiConsoleErrors.UNSERIALIZABLE_TYPE.registerFactory(AddSerializerFix) + } +} diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt index 16e6e2cf8..4130b6ce3 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/PluginDataValuesChecker.kt @@ -56,8 +56,8 @@ class PluginDataValuesChecker : DeclarationChecker { if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME)) // TODO: 2020/9/18 external serializers return@forEach context.report(MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on( inspectionTarget, - type.fqName?.asString().toString()) - ) + classDescriptor + )) } } } \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AddSerializerFix.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AddSerializerFix.kt new file mode 100644 index 000000000..c276b3c8c --- /dev/null +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AddSerializerFix.kt @@ -0,0 +1,44 @@ +package net.mamoe.mirai.console.intellij.diagnostics.fix + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME +import net.mamoe.mirai.console.compiler.common.castOrNull +import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1 +import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix +import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction +import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory +import org.jetbrains.kotlin.idea.util.addAnnotation +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtModifierListOwner + +/** + * @see MiraiConsoleErrors.UNSERIALIZABLE_TYPE + */ +class AddSerializerFix( + element: KtClassOrObject, +) : KotlinCrossLanguageQuickFixAction(element), KotlinUniversalQuickFix { + + override fun getFamilyName(): String = "添加注解" + override fun getText(): String = "添加 @Serializable" + + override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { + element?.addAnnotation(SERIALIZABLE_FQ_NAME) ?: return + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val classDescriptor = diagnostic.castOrNull>()?.a?.castOrNull() ?: return null + val ktClassOrObject = classDescriptor.findPsi()?.castOrNull() ?: return null + return AddSerializerFix(ktClassOrObject) + } + + override fun isApplicableForCodeFragment(): Boolean = false + } +} diff --git a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml index c7d524f96..19db4ac87 100644 --- a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -26,7 +26,7 @@ - + From c8696b6ac5a2e3bf1e60c538f8183a7e63fad7f2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 13:33:15 +0800 Subject: [PATCH 053/114] Update debugging proejcts --- .../org/example/myplugin/MyPluginMain.kt | 9 +++--- .../diagnostics/fix/AbuseYellowIntention.kt | 30 +++++++++++++++++++ .../src/main/resources/META-INF/plugin.xml | 7 +++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AbuseYellowIntention.kt diff --git a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt index a8f64c0fe..b010e4c9f 100644 --- a/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt +++ b/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt @@ -8,7 +8,7 @@ import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin -val T = "scas" + "pp" // 编译期常量 +val T = "org.example" // 编译期常量 object MyPluginMain : KotlinPlugin( JvmPluginDescription( @@ -16,7 +16,6 @@ object MyPluginMain : KotlinPlugin( "0.1.0", ) { name(".") - id("") } ) { override fun onEnable() { @@ -29,7 +28,7 @@ object MyPluginMain : KotlinPlugin( } } -object DataTest : AutoSavePluginConfig() { +object DataTest : AutoSavePluginConfig("data") { val p by value() val pp by value() } @@ -41,4 +40,6 @@ data class HasDefaultValue( data class NoDefaultValue( val y: Int, -) \ No newline at end of file +) + +val y = "傻逼 yellow" \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AbuseYellowIntention.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AbuseYellowIntention.kt new file mode 100644 index 000000000..fbb08fea4 --- /dev/null +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/diagnostics/fix/AbuseYellowIntention.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.intellij.diagnostics.fix +/* + +import com.intellij.openapi.editor.Editor +import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues +import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtStringTemplateExpression + +@Suppress("IntentionDescriptionNotFoundInspection") // +class AbuseYellowIntention : + SelfTargetingIntention(KtStringTemplateExpression::class.java, { "Abuse yellow" }, { "Abuse yellow" }) { + override fun applyTo(element: KtStringTemplateExpression, editor: Editor?) { + element.replace(KtPsiFactory(element).createExpression("\"弱智黄色\"")) + } + + override fun isApplicableTo(element: KtStringTemplateExpression, caretOffset: Int): Boolean { + return element.resolveStringConstantValues().firstOrNull() == "黄色" + } + +}*/ \ No newline at end of file diff --git a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml index 19db4ac87..f9f0b0e8f 100644 --- a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -21,6 +21,13 @@ implementationClass="net.mamoe.mirai.console.intellij.line.marker.CommandDeclarationLineMarkerProvider"/> + + From ceb689066eda7045c17691aeced4e14f70714c7a Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 13:48:23 +0800 Subject: [PATCH 054/114] Introduce RestrictedScope for further resolution for IDE plugin --- .../console/compiler/common/ResolveContext.kt | 19 ++++++------- .../compiler/common/RestrictedScope.kt | 28 +++++++++++++++++++ .../mamoe/mirai/console/data/PluginData.kt | 4 +-- .../mirai/console/plugin/jvm/JvmPlugin.kt | 4 +++ .../diagnostics/MiraiConsoleErrors.java | 3 ++ .../MiraiConsoleErrorsRendering.kt | 14 ++++++++++ 6 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/RestrictedScope.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt index 8a3702b69..9dff987e2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/ResolveContext.kt @@ -36,19 +36,16 @@ public annotation class ResolveContext( // ConstantKind /////////////////////////////////////////////////////////////////////////// - PLUGIN_ID, - PLUGIN_NAME, - PLUGIN_VERSION, + PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION + PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION + PLUGIN_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION - COMMAND_NAME, + COMMAND_NAME, // ILLEGAL_COMMAND_NAME - PERMISSION_NAMESPACE, - PERMISSION_NAME, - PERMISSION_ID, // for parseFromString + PERMISSION_NAMESPACE, // ILLEGAL_COMMAND_NAMESPACE + PERMISSION_NAME, // ILLEGAL_COMMAND_NAME + PERMISSION_ID, // ILLEGAL_COMMAND_ID - /** - * Custom serializers allowed - */ - RESTRICTED_NO_ARG_CONSTRUCTOR, + RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/RestrictedScope.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/RestrictedScope.kt new file mode 100644 index 000000000..640016cc7 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/compiler/common/RestrictedScope.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.compiler.common + +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import kotlin.annotation.AnnotationTarget.FUNCTION + +/** + * 标记一个函数, 在其函数体内限制特定一些函数的使用. + */ +@ConsoleExperimentalApi +@Target(FUNCTION) +@Retention(AnnotationRetention.BINARY) +public annotation class RestrictedScope( + vararg val kinds: Kind, +) { + public enum class Kind { + PERMISSION_REGISTER, // ILLEGAL_PERMISSION_REGISTER_USE + COMMAND_REGISTER, // ILLEGAL_COMMAND_REGISTER_USE + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index c8c01736d..174170e1b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -217,8 +217,8 @@ public inline fun PluginData.value( */ @ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) @LowPriorityInOverloadResolution -public inline fun PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue = - valueImpl(typeOf0(), T::class).also { it.value.apply() } +public inline fun <@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) reified T> + PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue = valueImpl(typeOf0(), T::class).also { it.value.apply() } @Suppress("UNCHECKED_CAST") @PublishedApi diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt index c76c3ee6a..d3aa7e7cb 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt @@ -18,6 +18,9 @@ package net.mamoe.mirai.console.plugin.jvm import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.console.compiler.common.RestrictedScope +import net.mamoe.mirai.console.compiler.common.RestrictedScope.Kind.COMMAND_REGISTER +import net.mamoe.mirai.console.compiler.common.RestrictedScope.Kind.PERMISSION_REGISTER import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.permission.PermissionIdNamespace import net.mamoe.mirai.console.plugin.Plugin @@ -59,6 +62,7 @@ public interface JvmPlugin : Plugin, CoroutineScope, * * @receiver 组件容器 */ + @RestrictedScope(COMMAND_REGISTER, PERMISSION_REGISTER) public fun PluginComponentStorage.onLoad() {} /** diff --git a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java index e4e2e21da..a8181fb84 100644 --- a/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java +++ b/tools/compiler-common/src/main/java/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrors.java @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.Errors; +import org.jetbrains.kotlin.psi.KtNamedDeclaration; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; @@ -25,6 +26,8 @@ public interface MiraiConsoleErrors { DiagnosticFactory2 ILLEGAL_PERMISSION_NAME = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 ILLEGAL_PERMISSION_ID = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 ILLEGAL_PERMISSION_NAMESPACE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_COMMAND_REGISTER_USE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 ILLEGAL_PERMISSION_REGISTER_USE = DiagnosticFactory2.create(ERROR); @Deprecated Object _init = new Object() { diff --git a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt index e3e7b7b6a..ca79ec5b3 100644 --- a/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt +++ b/tools/compiler-common/src/main/kotlin/net/mamoe/mirai/console/compiler/common/diagnostics/MiraiConsoleErrorsRendering.kt @@ -61,6 +61,20 @@ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { Renderers.STRING, Renderers.STRING, ) + + put( + ILLEGAL_COMMAND_REGISTER_USE, + "''{0}'' 无法使用在 ''{1}'' 环境下.", + Renderers.DECLARATION_NAME, + Renderers.STRING + ) + + put( + ILLEGAL_PERMISSION_REGISTER_USE, + "''{0}'' 无法使用在 ''{1}'' 环境下.", + Renderers.DECLARATION_NAME, + Renderers.STRING + ) } override fun getMap() = MAP From 48f5c947b6863d338c34ff843c89347e6a5630b5 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 13:49:01 +0800 Subject: [PATCH 055/114] Add experimental API notes --- .../kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index 21fbd2239..926fcd19a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageChain import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationTarget.FUNCTION @@ -118,6 +119,7 @@ public abstract class CompositeCommand( protected annotation class Description(val value: String) /** 参数名, 将参与构成 [usage] */ + @ConsoleExperimentalApi("Classname might change") @Retention(RUNTIME) @Target(AnnotationTarget.VALUE_PARAMETER) protected annotation class Name(val value: String) From 799123c331bf40538f1f471bcbf76cfcab54cfbf Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 14:31:21 +0800 Subject: [PATCH 056/114] Add Version Selection --- README.md | 13 ++++++++++++- docs/README.md | 20 ++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ad76bb581..604df3936 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ Mirai 是一个在全平台下运行,提供 QQ 协议支持的高效率机器 # mirai-console -[ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-console/) 高效率插件支持 QQ 机器人框架, 机器人核心来自 [mirai](https://github.com/mamoe/mirai) @@ -35,6 +34,18 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. **注意:`mirai-console` 后端和 terminal 前端正在进行完全的重构, 所有 API 都不具有稳定性** +### 版本 + +[Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg? + +详见 [版本规范](docs/README.md#版本规范) + +| 版本类型 | 版本号 | +|:------:|:----------:| +| 稳定 | - | +| 预览 | 1.0-M4 | +| 开发 | ![Version] | + ### 使用 **查看示例插件**: [mirai-console-example-plugin](https://github.com/Him188/mirai-console-example-plugin) diff --git a/docs/README.md b/docs/README.md index 3125e4e4e..6db0f09d9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -143,12 +143,28 @@ Mirai Console 是不断前进的框架,将来必定会发生 API 弃用和重 Mirai Console 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。 -在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一些功能基本完成,但还不稳定。 -但这些版本里新增的 API 可能还会在下一个 Milestone 版本变化,因此请按需使用。 +在日常开发中, Mirai Console 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。 + +在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。 +这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。 在大版本即将发布前,Mirai Console 会以 `-RC` 版本后缀发布最终的预览版本。 `RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。 +##### 版本选择 + +**稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。 + +| 目的 | 推荐至少更新到版本 | +|:--------------------------:|:--------------:| +| 生产环境 | `x.y.z` | +| 希望尽早体验稳定新特性的插件作者 | `-RC` | +| 无论如何都想体验新特性的插件作者 | `-M` | +| 前端实现者, 底层插件作者 | `-M` | +| 为 Mirai Console 提交 PR | `-dev` | + +其中,‘底层插件’ 表示提供扩展等的插件。如权限系统,其他语言插件加载器等。 + ##### 更新兼容性 对于 `x.y.z` 版本号: From 06d0ee4653ac20b4bf97d675ce914c9509987f26 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 14:35:32 +0800 Subject: [PATCH 057/114] Add Version guide --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 604df3936..799a6d81e 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,15 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. ### 版本 [Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg? +[Bintray Download]: https://bintray.com/him188moe/mirai/mirai-console/ 详见 [版本规范](docs/README.md#版本规范) -| 版本类型 | 版本号 | -|:------:|:----------:| -| 稳定 | - | -| 预览 | 1.0-M4 | -| 开发 | ![Version] | +| 版本类型 | 版本号 | +|:------:|:------------:| +| 稳定 | - | +| 预览 | 1.0-M4 | +| 开发 | [![Version]][Bintray Download] | ### 使用 @@ -54,7 +55,6 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. #### Gradle `CORE_VERSION`: [ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-core/) -`CONSOLE_VERSION`: [ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-console/) build.gradle.kts @@ -71,5 +71,7 @@ dependencies { } ``` +**注意:`mirai-console` 1.0-RC 发布之前, 前端请使用 `mirai-console-pure` 而不是 `mirai-console-terminal`** + #### Maven 同理 Gradle From bade699a1f9e67680b49dfd44202502639256b33 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 14:36:43 +0800 Subject: [PATCH 058/114] 1.0-dev-5 --- buildSrc/src/main/kotlin/Versions.kt | 2 +- tools/intellij-plugin/src/main/resources/META-INF/plugin.xml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 322a3d368..9aa94ebcd 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-4" + const val console = "1.0-RC-dev-5" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml index f9f0b0e8f..c8a86291b 100644 --- a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -35,7 +35,4 @@ implementation="net.mamoe.mirai.console.intellij.IDEContainerContributor"/> - - - \ No newline at end of file From 6c6ce1ab684536dc2096bce9a5f211c522ab8bc6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 14:50:12 +0800 Subject: [PATCH 059/114] Update plugin description --- tools/intellij-plugin/build.gradle.kts | 12 +++++++++++- .../src/main/resources/META-INF/plugin.xml | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tools/intellij-plugin/build.gradle.kts b/tools/intellij-plugin/build.gradle.kts index 3520633be..7dba0bebc 100644 --- a/tools/intellij-plugin/build.gradle.kts +++ b/tools/intellij-plugin/build.gradle.kts @@ -50,8 +50,18 @@ tasks.getByName("publishPlugin", org.jetbrains.intellij.tasks.PublishTask::class tasks.withType { sinceBuild("193.*") untilBuild("205.*") + pluginDescription(""" + Plugin development support for Mirai Conosle + +

Features

+
    +
  • Inspections for plugin properties, for example, checking PluginDescription.
  • +
  • Inspections for illegal calls.
  • +
  • Intentions for resolving serialization problems.
  • +
+ """.trimIndent()) changeNotes(""" - Fix cancellation on analyzing augments + Initial release """.trimIndent()) } diff --git a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml index c8a86291b..935b52791 100644 --- a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ - net.mamoe.mirai-console-dev + net.mamoe.mirai-console - Mirai Console Dev + Mirai Console Date: Sat, 19 Sep 2020 14:58:32 +0800 Subject: [PATCH 060/114] Add pluginIcon.svg --- .../src/main/resources/icons/pluginIcon.svg | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tools/intellij-plugin/src/main/resources/icons/pluginIcon.svg diff --git a/tools/intellij-plugin/src/main/resources/icons/pluginIcon.svg b/tools/intellij-plugin/src/main/resources/icons/pluginIcon.svg new file mode 100644 index 000000000..72a9d1a7b --- /dev/null +++ b/tools/intellij-plugin/src/main/resources/icons/pluginIcon.svg @@ -0,0 +1,119 @@ + + + + + Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + From ad3124fe1036084a08b4199e783db8dbc63a5a22 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 20:33:34 +0800 Subject: [PATCH 061/114] Gradle plugin --- build.gradle.kts | 7 +- buildSrc/src/main/kotlin/Versions.kt | 2 +- tools/gradle-plugin/build.gradle.kts | 143 +++++++++++----- .../gradle/IGNORED_DEPENDENCIES_IN_SHADOW.kt | 132 +++++++++++++++ .../console/gradle/MiraiConsoleExtension.kt | 126 ++++++++++++++ .../gradle/MiraiConsoleGradlePlugin.kt | 156 ++++++++++++++++++ .../mirai/console/gradle/VersionConstants.kt | 15 ++ 7 files changed, 533 insertions(+), 48 deletions(-) create mode 100644 tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/IGNORED_DEPENDENCIES_IN_SHADOW.kt create mode 100644 tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleExtension.kt create mode 100644 tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleGradlePlugin.kt create mode 100644 tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4d51ad6de..ca24e980d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,12 @@ @file:Suppress("UnstableApiUsage") plugins { - id("com.jfrog.bintray") version Versions.bintray apply false - id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge apply false kotlin("jvm") version Versions.kotlinCompiler kotlin("plugin.serialization") version Versions.kotlinCompiler + id("com.jfrog.bintray") version Versions.bintray apply false + id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge apply false + id("com.gradle.plugin-publish") version "0.12.0" apply false + //id("com.bmuschko.nexus") version "2.3.1" apply false + //id("io.codearte.nexus-staging") version "0.11.0" apply false } tasks.withType(JavaCompile::class.java) { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 9aa94ebcd..ef3a362e1 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-5" + const val console = "1.0-RC-dev-28" const val consoleGraphical = "0.0.7" const val consoleTerminal = console diff --git a/tools/gradle-plugin/build.gradle.kts b/tools/gradle-plugin/build.gradle.kts index 741181c4b..88ec22487 100644 --- a/tools/gradle-plugin/build.gradle.kts +++ b/tools/gradle-plugin/build.gradle.kts @@ -2,14 +2,112 @@ plugins { kotlin("jvm") + kotlin("kapt") + id("java-gradle-plugin") + id("com.gradle.plugin-publish") id("java") + //signing `maven-publish` id("com.jfrog.bintray") + + id("com.github.johnrengelman.shadow") +} + +dependencies { + compileOnly(gradleApi()) + compileOnly(kotlin("gradle-plugin-api").toString()) { + exclude("org.jetbrains.kotlin", "kotlin-stdlib") + } + compileOnly(kotlin("gradle-plugin").toString()) { + exclude("org.jetbrains.kotlin", "kotlin-stdlib") + } + + compileOnly(kotlin("stdlib")) + + api("com.github.jengelman.gradle.plugins:shadow:6.0.0") + api("org.jetbrains:annotations:19.0.0") +} + +dependencies { + testApi(kotlin("test")) + testApi(kotlin("test-junit5")) + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") } version = Versions.console description = "Gradle plugin for Mirai Console" +pluginBundle { + website = "https://github.com/mamoe/mirai-console" + vcsUrl = "https://github.com/mamoe/mirai-console" + tags = listOf("framework", "kotlin", "mirai") +} + +gradlePlugin { + plugins { + create("miraiConsole") { + id = "net.mamoe.mirai-console" + displayName = "Mirai Console" + description = project.description + implementationClass = "net.mamoe.mirai.console.gradle.MiraiConsoleGradlePlugin" + } + } +} + +kotlin { + sourceSets.all { + target.compilations.all { + kotlinOptions { + apiVersion = "1.3" + languageVersion = "1.3" + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + } + } + languageSettings.apply { + progressiveMode = true + + useExperimentalAnnotation("kotlin.RequiresOptIn") + useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") + useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") + useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") + } + } +} + +tasks { + "test"(Test::class) { + useJUnitPlatform() + } + + val compileKotlin by getting {} + + @Suppress("UNUSED_VARIABLE") + val fillBuildConstants by registering { + group = "mirai" + doLast { + (compileKotlin as org.jetbrains.kotlin.gradle.tasks.KotlinCompile).source.filter { it.name == "VersionConstants.kt" }.single() + .let { file -> + file.writeText( + file.readText() + .replace( + Regex("""const val CONSOLE_VERSION = ".*"""") + ) { + """const val CONSOLE_VERSION = "${Versions.console}"""" + } + .replace( + Regex("""const val CORE_VERSION = ".*"""") + ) { """const val CORE_VERSION = "${Versions.core}"""" } + ) + } + } + } + + compileKotlin.dependsOn(fillBuildConstants) +} + java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -19,49 +117,4 @@ tasks.withType(JavaCompile::class.java) { options.encoding = "UTF8" } -kotlin { - sourceSets.all { - target.compilations.all { - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" - //useIR = true - } - } - languageSettings.apply { - progressiveMode = true - - useExperimentalAnnotation("kotlin.Experimental") - useExperimentalAnnotation("kotlin.RequiresOptIn") - - useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI") - useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI") - useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") - useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi") - useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") - useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference") - useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts") - useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi") - useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi") - } - } -} - -dependencies { - api("org.jetbrains:annotations:19.0.0") - api(kotlinx("coroutines-jdk8", Versions.coroutines)) - - testApi(kotlin("test")) - testApi(kotlin("test-junit5")) - - testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") -} - -tasks { - "test"(Test::class) { - useJUnitPlatform() - } -} - // setupPublishing("mirai-console-gradle") \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/IGNORED_DEPENDENCIES_IN_SHADOW.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/IGNORED_DEPENDENCIES_IN_SHADOW.kt new file mode 100644 index 000000000..4b619f28f --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/IGNORED_DEPENDENCIES_IN_SHADOW.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:JvmMultifileClass +@file:JvmName("MiraiConsoleGradlePluginKt") + +package net.mamoe.mirai.console.gradle + +internal val IGNORED_DEPENDENCIES_IN_SHADOW = arrayOf( + "org.jetbrains.kotlin:kotlin-stdlib", + "org.jetbrains.kotlin:kotlin-stdlib-common", + "org.jetbrains.kotlin:kotlin-stdlib-metadata", + "org.jetbrains.kotlin:kotlin-stdlib-jvm", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + + "org.jetbrains.kotlin:kotlin-reflect", + "org.jetbrains.kotlin:kotlin-reflect-common", + "org.jetbrains.kotlin:kotlin-reflect-metadata", + "org.jetbrains.kotlin:kotlin-reflect-jvm", + + "org.jetbrains.kotlinx:kotlinx-serialization-core", + "org.jetbrains.kotlinx:kotlinx-serialization-core-common", + "org.jetbrains.kotlinx:kotlinx-serialization-core-metadata", + "org.jetbrains.kotlinx:kotlinx-serialization-core-jvm", + + "org.jetbrains.kotlinx:kotlinx-serialization-runtime", + "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common", + "org.jetbrains.kotlinx:kotlinx-serialization-runtime-metadata", + "org.jetbrains.kotlinx:kotlinx-serialization-runtime-jvm", + + "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", + "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-common", + "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-metadata", + "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-jvm", + + "org.jetbrains.kotlinx:kotlinx-coroutines-core", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-common", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-metadata", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", + + "org.jetbrains.kotlinx:kotlinx-io", + "org.jetbrains.kotlinx:kotlinx-io-common", + "org.jetbrains.kotlinx:kotlinx-io-metadata", + "org.jetbrains.kotlinx:kotlinx-io-jvm", + + "org.jetbrains.kotlinx:kotlinx-coroutines-io", + "org.jetbrains.kotlinx:kotlinx-coroutines-io-common", + "org.jetbrains.kotlinx:kotlinx-coroutines-io-metadata", + "org.jetbrains.kotlinx:kotlinx-coroutines-io-jvm", + + "org.jetbrains.kotlinx:kotlinx-coroutines-core-jdk7", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-jdk8", + + "org.jetbrains.kotlinx:kotlinx-coroutines-jdk7", + "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", + + "org.jetbrains.kotlinx:atomicFu", + "org.jetbrains.kotlinx:atomicFu-common", + "org.jetbrains.kotlinx:atomicFu-metadata", + "org.jetbrains.kotlinx:atomicFu-jvm", + + "org.jetbrains:annotations:19.0.0", + + "io.ktor:ktor-client", + "io.ktor:ktor-client-common", + "io.ktor:ktor-client-metadata", + "io.ktor:ktor-client-jvm", + + "io.ktor:ktor-client-cio", + "io.ktor:ktor-client-cio-common", + "io.ktor:ktor-client-cio-metadata", + "io.ktor:ktor-client-cio-jvm", + + "io.ktor:ktor-client-core", + "io.ktor:ktor-client-core-common", + "io.ktor:ktor-client-core-metadata", + "io.ktor:ktor-client-core-jvm", + + "io.ktor:ktor-client-network", + "io.ktor:ktor-client-network-common", + "io.ktor:ktor-client-network-metadata", + "io.ktor:ktor-client-network-jvm", + + "io.ktor:ktor-client-util", + "io.ktor:ktor-client-util-common", + "io.ktor:ktor-client-util-metadata", + "io.ktor:ktor-client-util-jvm", + + "io.ktor:ktor-client-http", + "io.ktor:ktor-client-http-common", + "io.ktor:ktor-client-http-metadata", + "io.ktor:ktor-client-http-jvm", + + "org.bouncyCastle:bcProv-jdk15on", + + "net.mamoe:mirai-core", + "net.mamoe:mirai-core-metadata", + "net.mamoe:mirai-core-common", + "net.mamoe:mirai-core-jvm", + + "net.mamoe:mirai-core-api", // for future + "net.mamoe:mirai-core-api-metadata", + "net.mamoe:mirai-core-api-common", + "net.mamoe:mirai-core-api-jvm", + + "net.mamoe:mirai-core-qqAndroid", + "net.mamoe:mirai-core-qqAndroid-metadata", + "net.mamoe:mirai-core-qqAndroid-common", + "net.mamoe:mirai-core-qqAndroid-jvm", + + "net.mamoe:mirai-console", + "net.mamoe:mirai-console-api", // for future + "net.mamoe:mirai-console-terminal", + "net.mamoe:mirai-console-graphical", + + "net.mamoe.yamlKt:yamlKt", + "net.mamoe.yamlKt:yamlKt-common", + "net.mamoe.yamlKt:yamlKt-metadata", + "net.mamoe.yamlKt:yamlKt-jvm", + + "net.mamoe:kotlin-jvm-blocking-bridge", + "net.mamoe:kotlin-jvm-blocking-bridge-common", + "net.mamoe:kotlin-jvm-blocking-bridge-metadata", + "net.mamoe:kotlin-jvm-blocking-bridge-jvm" +).map { it.toLowerCase() }.map { MiraiConsoleExtension.ExcludedDependency(it.substringBefore(':'), it.substringAfterLast(':')) }.toTypedArray() \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleExtension.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleExtension.kt new file mode 100644 index 000000000..74dc3043a --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleExtension.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.console.gradle + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.JavaVersion + +/** + * ``` + * mirai { + * // 配置 + * } + * ``` + */ +// must be open +open class MiraiConsoleExtension { + /** + * 为 `true` 时不自动添加 mirai-core 的依赖 + * + * 默认: `false` + */ + var noCore: Boolean = false + + /** + * 为 `true` 时不自动为 test 模块添加 mirai-core-qqandroid 的依赖. + * + * 默认: `false` + */ + var noTestCoreQQAndroid: Boolean = false + + /** + * 为 `true` 时不自动添加 mirai-console 的依赖. + * + * 默认: `false` + */ + var noConsole: Boolean = false + + /** + * 自动添加的 mirai-core 和 mirai-core-qqandroid 的版本. + * + * 默认: 与本 Gradle 插件编译时的 mirai-core 版本相同. [VersionConstants.CORE_VERSION] + */ + var coreVersion: String = VersionConstants.CORE_VERSION + + /** + * 自动添加的 mirai-console 后端和前端的版本. + * + * 默认: 与本 Gradle 插件版本相同. [VersionConstants.CONSOLE_VERSION] + */ + var consoleVersion: String = VersionConstants.CONSOLE_VERSION + + /** + * 自动为 test 模块添加的前端依赖名称 + * + * 为 `null` 时不自动为 test 模块添加前端依赖. + * + * 默认: [MiraiConsoleFrontEndKind.TERMINAL] + */ + var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL + + /** + * Java 和 Kotlin 编译目标. 至少为 [JavaVersion.VERSION_1_8]. + * + * 一般人不需要修改此项. + * + * 默认: [JavaVersion.VERSION_1_8] + */ + var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8 + + /** + * 默认会配置 Kotlin 编译器参数 "-Xjvm-default=all". 将此项设置为 `false` 可避免配置. + * + * 一般人不需要修改此项. + * + * 默认: `false` + */ + var dontConfigureKotlinJvmDefault: Boolean = false + + internal val shadowConfigurations: MutableList Unit> = mutableListOf() + internal val excludedDependencies: MutableSet = mutableSetOf() + + internal data class ExcludedDependency( + val group: String, + val name: String + ) + + /** + * 配置 [ShadowJar] (即打包插件) + */ + fun configureShadow(configure: ShadowJar.() -> Unit) { + shadowConfigurations.add(configure) + } + + /** + * 在插件打包时忽略一个依赖 + * + * @param notation 格式为 "groupId:name". 如 "org.jetbrains.kotlin:kotlin-stdlib" + */ + fun excludeDependency(notation: String) { + requireNotNull(notation.count { it == ':' } == 1) { "Invalid dependency notation $notation." } + excludedDependencies.add(ExcludedDependency(notation.substringBefore(':'), notation.substringAfter(':'))) + } + + /** + * 在插件打包时忽略一个依赖 + * + * @param group 如 "org.jetbrains.kotlin" + * @param name 如 "kotlin-stdlib" + */ + fun excludeDependency(group: String, name: String) { + excludedDependencies.add(ExcludedDependency(group, name)) + } +} + +enum class MiraiConsoleFrontEndKind { + TERMINAL, +} \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleGradlePlugin.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleGradlePlugin.kt new file mode 100644 index 000000000..051502390 --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleGradlePlugin.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:JvmMultifileClass +@file:JvmName("MiraiConsoleGradlePluginKt") + +package net.mamoe.mirai.console.gradle + +import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.compile.JavaCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME +import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget + +class MiraiConsoleGradlePlugin : Plugin { + companion object { + internal const val BINTRAY_REPOSITORY_URL = "https://dl.bintray.com/him188moe/mirai" + } + + private fun KotlinSourceSet.configureSourceSet(project: Project) { + languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn") + dependencies { configureDependencies(project, this@configureSourceSet) } + } + + private fun Project.configureTarget(target: KotlinTarget) { + val miraiExtension = project.miraiExtension + + for (compilation in target.compilations) with(compilation) { + kotlinOptions { + if (this !is KotlinJvmOptions) return@kotlinOptions + jvmTarget = miraiExtension.jvmTarget.toString() + if (!miraiExtension.dontConfigureKotlinJvmDefault) freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + } + } + target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet -> + sourceSet.configureSourceSet(project) + } + } + + @Suppress("SpellCheckingInspection") + private fun KotlinDependencyHandler.configureDependencies(project: Project, sourceSet: KotlinSourceSet) { + val miraiExtension = project.miraiExtension + + if (!miraiExtension.noCore) compileOnly("net.mamoe:mirai-core:${miraiExtension.coreVersion}") + if (!miraiExtension.noConsole) compileOnly("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") + + if (sourceSet.name.endsWith("test", ignoreCase = true)) { + if (!miraiExtension.noCore) api("net.mamoe:mirai-core:${miraiExtension.coreVersion}") + if (!miraiExtension.noConsole) api("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") + if (!miraiExtension.noTestCoreQQAndroid) api("net.mamoe:mirai-core-qqandroid:${miraiExtension.coreVersion}") + when (miraiExtension.useTestConsoleFrontEnd) { + MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}") + } + } + } + + private fun Project.configureCompileTarget() { + extensions.findByType(JavaPluginExtension::class.java)?.apply { + val miraiExtension = miraiExtension + sourceCompatibility = miraiExtension.jvmTarget + targetCompatibility = miraiExtension.jvmTarget + } + + tasks.withType(JavaCompile::class.java) { + it.options.encoding = "UTF8" + } + } + + private fun Project.registerBuildPluginTasks() { + val miraiExtension = this.miraiExtension + + tasks.findByName("shadowJar")?.enabled = false + + fun registerBuildPluginTask(target: KotlinTarget, isSinglePlatform: Boolean) { + tasks.create(if (isSinglePlatform) "buildPlugin" else "buildPlugin${target.name.capitalize()}", ShadowJar::class.java).apply shadow@{ + group = "mirai" + + val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME } + + compilations.forEach { + dependsOn(it.compileKotlinTask) + from(it.output) + } + + from(project.configurations.getByName("runtimeClasspath").copyRecursive { dependency -> + for (excludedDependency in IGNORED_DEPENDENCIES_IN_SHADOW + miraiExtension.excludedDependencies) { + if (excludedDependency.group == dependency.group + && excludedDependency.name == dependency.name + ) return@copyRecursive false + } + true + }) + + exclude { file -> + file.name.endsWith(".sf", ignoreCase = true) + } + + destinationDirectory.value(project.layout.projectDirectory.dir(project.buildDir.name).dir("mirai")) + + miraiExtension.shadowConfigurations.forEach { it.invoke(this@shadow) } + } + } + + val targets = kotlinTargets + val isSingleTarget = targets.size == 1 + targets.forEach { target -> + registerBuildPluginTask(target, isSingleTarget) + } + } + + override fun apply(target: Project): Unit = with(target) { + target.extensions.create("mirai", MiraiConsoleExtension::class.java) + + target.plugins.apply(JavaPlugin::class.java) + target.plugins.apply(ShadowPlugin::class.java) + + target.repositories.maven { it.setUrl(BINTRAY_REPOSITORY_URL) } + + afterEvaluate { + configureCompileTarget() + registerBuildPluginTasks() + kotlinTargets.forEach { configureTarget(it) } + } + } +} + +internal val Project.miraiExtension: MiraiConsoleExtension + get() = extensions.findByType(MiraiConsoleExtension::class.java) ?: error("Cannot find MiraiConsoleExtension in project ${this.name}") + +internal val Project.kotlinTargets: Collection + get() { + val kotlinExtension = extensions.findByType(KotlinProjectExtension::class.java) + ?: error("Kotlin plugin not applied. Please read https://www.kotlincn.net/docs/reference/using-gradle.html") + + return when (kotlinExtension) { + is KotlinMultiplatformExtension -> kotlinExtension.targets + is KotlinSingleTargetExtension -> listOf(kotlinExtension.target) + else -> error("[MiraiConsole] Internal error: kotlinExtension is neither KotlinMultiplatformExtension nor KotlinSingleTargetExtension") + } + } \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt new file mode 100644 index 000000000..2db0c82e2 --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.gradle + +internal object VersionConstants { + const val CONSOLE_VERSION = "1.0-RC-dev-28" // value is written here automatically during build + const val CORE_VERSION = "1.3.0" // value is written here automatically during build +} \ No newline at end of file From 2896a2266b0749058f1b3cff1130338a7e380ca7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 21:01:04 +0800 Subject: [PATCH 062/114] Setup gradle publish --- .github/workflows/bintray.yml | 37 ------------------ .github/workflows/push.yml | 53 ++++++++++++++++++++++++++ tools/compiler-common/build.gradle.kts | 2 +- tools/gradle-plugin/build.gradle.kts | 2 +- tools/intellij-plugin/build.gradle.kts | 2 +- 5 files changed, 56 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/bintray.yml create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/bintray.yml b/.github/workflows/bintray.yml deleted file mode 100644 index 425caf74b..000000000 --- a/.github/workflows/bintray.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Bintray Publish - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch -on: - release: - types: - - created - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Gradle clean - run: ./gradlew clean - - name: Gradle build - run: ./gradlew build # if test's failed, don't publish - - name: Check keys - run: ./gradlew :mirai-console:ensureBintrayAvailable -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - - name: Gradle :mirai-console:fillBuildConstants - run: ./gradlew :mirai-console:fillBuildConstants -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - - name: Gradle :mirai-console:bintrayUpload - run: ./gradlew :mirai-console:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - - name: Gradle :mirai-console-terminal:bintrayUpload - run: ./gradlew :mirai-console-terminal:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..90828d547 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,53 @@ +# This is a basic workflow to help you get started with Actions + +name: Bintray Publish + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + release: + types: + - created + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Gradle clean + run: ./gradlew clean + - name: Gradle build + run: ./gradlew build # if test's failed, don't publish + - name: Check keys + run: ./gradlew + :mirai-console:ensureBintrayAvailable + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console:fillBuildConstants + run: ./gradlew + :mirai-console:fillBuildConstants + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console:bintrayUpload + run: ./gradlew + :mirai-console:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-terminal:bintrayUpload + run: ./gradlew + :mirai-console-terminal:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Publish Gradle plugin + run: ./gradlew + :mirai-console-gradle:publishPlugins + -Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} + -Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} diff --git a/tools/compiler-common/build.gradle.kts b/tools/compiler-common/build.gradle.kts index 37295d0be..e733d5e55 100644 --- a/tools/compiler-common/build.gradle.kts +++ b/tools/compiler-common/build.gradle.kts @@ -71,4 +71,4 @@ tasks { } } -// setupPublishing("mirai-console-intellij") \ No newline at end of file +setupPublishing("mirai-console-compiler-common") \ No newline at end of file diff --git a/tools/gradle-plugin/build.gradle.kts b/tools/gradle-plugin/build.gradle.kts index 88ec22487..d83c375a2 100644 --- a/tools/gradle-plugin/build.gradle.kts +++ b/tools/gradle-plugin/build.gradle.kts @@ -117,4 +117,4 @@ tasks.withType(JavaCompile::class.java) { options.encoding = "UTF8" } -// setupPublishing("mirai-console-gradle") \ No newline at end of file +setupPublishing("mirai-console-gradle") \ No newline at end of file diff --git a/tools/intellij-plugin/build.gradle.kts b/tools/intellij-plugin/build.gradle.kts index 7dba0bebc..d71fce91d 100644 --- a/tools/intellij-plugin/build.gradle.kts +++ b/tools/intellij-plugin/build.gradle.kts @@ -116,4 +116,4 @@ tasks { } } -// setupPublishing("mirai-console-intellij") \ No newline at end of file +setupPublishing("mirai-console-intellij") \ No newline at end of file From 58e8a982ec8a3e55b2b4d0f2e517605457f40ef2 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 19 Sep 2020 21:03:22 +0800 Subject: [PATCH 063/114] Add mirai-console-example-plugin (Groovy DSL) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 799a6d81e..fe7570100 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,9 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. ### 使用 -**查看示例插件**: [mirai-console-example-plugin](https://github.com/Him188/mirai-console-example-plugin) +**查看示例插件** +- [mirai-console-example-plugin (Kotlin DSL)](https://github.com/Him188/mirai-console-example-plugin) +- [mirai-console-example-plugin (Groovy DSL)](https://github.com/Karlatemp/mirai-console-example-plugin) 正在更新中的文档:[参考文档](docs/README.md) From 9dcfa43292b1bd8396fc38d039a1d4f0732e85ee Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 21:25:13 +0800 Subject: [PATCH 064/114] Update Publishing.yml --- .github/workflows/{push.yml => Publishing.yml} | 10 ++++++++++ 1 file changed, 10 insertions(+) rename .github/workflows/{push.yml => Publishing.yml} (78%) diff --git a/.github/workflows/push.yml b/.github/workflows/Publishing.yml similarity index 78% rename from .github/workflows/push.yml rename to .github/workflows/Publishing.yml index 90828d547..12a8e5450 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/Publishing.yml @@ -46,6 +46,16 @@ jobs: :mirai-console-terminal:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-compiler-common:bintrayUpload + run: ./gradlew + :mirai-console-compiler-common:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-intellij:bintrayUpload + run: ./gradlew + :mirai-console-intellij:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Publish Gradle plugin run: ./gradlew :mirai-console-gradle:publishPlugins From ec03f8322d408bcda8e45a5a74a01363c2dac7e8 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 23:29:28 +0800 Subject: [PATCH 065/114] Update docs --- README.md | 71 ++----- .../internal/MiraiConsoleBuildConstants.kt | 4 +- buildSrc/src/main/kotlin/PublishingHelpers.kt | 2 +- docs/Appendix.md | 49 +++++ docs/ConfiguringProjects.md | 80 ++++++++ docs/Contributing.md | 2 + docs/Preparations.md | 103 ++++++++++ docs/README.md | 180 ------------------ docs/Run.md | 4 +- tools/gradle-plugin/README.md | 47 ++++- 10 files changed, 304 insertions(+), 238 deletions(-) create mode 100644 docs/Appendix.md create mode 100644 docs/ConfiguringProjects.md create mode 100644 docs/Contributing.md create mode 100644 docs/Preparations.md diff --git a/README.md b/README.md index fe7570100..9bc33196e 100644 --- a/README.md +++ b/README.md @@ -15,65 +15,32 @@ Mirai 是一个在全平台下运行,提供 QQ 协议支持的高效率机器 # mirai-console -高效率插件支持 QQ 机器人框架, 机器人核心来自 [mirai](https://github.com/mamoe/mirai) +高效率 QQ 机器人框架,机器人核心来自 [mirai](https://github.com/mamoe/mirai) -## 模块说明 +![Gradle CI](https://github.com/mamoe/mirai-console/workflows/Gradle%20CI/badge.svg?branch=master) +[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -console 由后端和前端一起工作. 使用时必须选择一个前端. +## 开发 -- `mirai-console`: console 的后端, 包含插件管理, 指令系统, 配置系统. +- **[准备工作 - 环境和前置知识](docs/Preparations.md)** +- **[配置项目](docs/ConfiguringProjects.md)** +- **[启动 Console](docs/Run.md)** + +### 后端插件开发基础 + +- 插件 - [Plugin 模块](docs/Plugins.md) +- 指令 - [Command 模块](docs/Commands.md) +- 存储 - [PluginData 模块](docs/PluginData.md) +- 权限 - [Permission 模块](docs/Permissions.md) - -前端: - -- `mirai-console-terminal`: console 的 Unix 终端界面前端. -- `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. (开发中) - -**注意:`mirai-console` 1.0-RC 发布之前, 前端请使用 `mirai-console-pure` 而不是 `mirai-console-terminal`** - -**注意:`mirai-console` 后端和 terminal 前端正在进行完全的重构, 所有 API 都不具有稳定性** - -### 版本 - -[Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg? -[Bintray Download]: https://bintray.com/him188moe/mirai/mirai-console/ - -详见 [版本规范](docs/README.md#版本规范) - -| 版本类型 | 版本号 | -|:------:|:------------:| -| 稳定 | - | -| 预览 | 1.0-M4 | -| 开发 | [![Version]][Bintray Download] | - -### 使用 - -**查看示例插件** +**示例插件**: - [mirai-console-example-plugin (Kotlin DSL)](https://github.com/Him188/mirai-console-example-plugin) - [mirai-console-example-plugin (Groovy DSL)](https://github.com/Karlatemp/mirai-console-example-plugin) -正在更新中的文档:[参考文档](docs/README.md) +### 后端插件开发进阶 -#### Gradle -`CORE_VERSION`: [ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-core/) +- 扩展 - [Extension 模块和扩展点](docs/Extensions.md) - -build.gradle.kts -```kotlin -repositories { - jcenter() -} - -dependencies { - implementation("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API - implementation("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端 - - testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试 -} -``` - -**注意:`mirai-console` 1.0-RC 发布之前, 前端请使用 `mirai-console-pure` 而不是 `mirai-console-terminal`** - -#### Maven -同理 Gradle +### 实现前端 +- [FrontEnd](docs/FrontEnd.md) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index 8ee34200d..19dd40076 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,8 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1599934775) + val buildDate: Instant = Instant.ofEpochSecond(1600522812) @JvmStatic - val version: Semver = Semver("1.0-M4", Semver.SemverType.LOOSE) + val version: Semver = Semver("1.0-RC-dev-28", Semver.SemverType.LOOSE) } diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index a6af10e47..69b954dac 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -54,7 +54,7 @@ internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publi inline fun Project.setupPublishing( artifactId: String, bintrayRepo: String = "mirai", - bintrayPkgName: String = "mirai-console", + bintrayPkgName: String = artifactId, vcs: String = "https://github.com/mamoe/mirai-console" ) { diff --git a/docs/Appendix.md b/docs/Appendix.md new file mode 100644 index 000000000..b8fd0b047 --- /dev/null +++ b/docs/Appendix.md @@ -0,0 +1,49 @@ +# Mirai Console - Appendix + +### Mirai Console 演进 + +Mirai Console 是不断前进的框架,将来必定会发生 API 弃用和重构。 +维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。 + +#### 版本规范 + +Mirai Console 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。 + +在日常开发中, Mirai Console 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。 + +在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。 +这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。 + +在大版本即将发布前,Mirai Console 会以 `-RC` 版本后缀发布最终的预览版本。 +`RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。 + +#### 版本选择 + +**稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。 + +| 目的 | 推荐至少更新到版本 | +|:--------------------------:|:--------------:| +| 生产环境 | `x.y.z` | +| 希望尽早体验稳定新特性的插件作者 | `-RC` | +| 无论如何都想体验新特性的插件作者 | `-M` | +| 前端实现者, 底层插件作者 | `-M` | +| 为 Mirai Console 提交 PR | `-dev` | + +其中,‘底层插件’ 表示提供扩展等的插件。如权限系统,其他语言插件加载器等。 + +#### 更新兼容性 + +对于 `x.y.z` 版本号: +- 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。 +- 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。 +- 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。 + +#### 弃用周期 + +一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。 + +如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。 +在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误); +在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。 + +`HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。 diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md new file mode 100644 index 000000000..6cd90981c --- /dev/null +++ b/docs/ConfiguringProjects.md @@ -0,0 +1,80 @@ +# Mirai Console - Configuring Projects + +配置 Mirai Console 项目。 + +## 模块说明 + +console 由后端和前端一起工作. 使用时必须选择一个前端. + +- `mirai-console`: Mirai Console 后端。 + +- `mirai-console-terminal`: 终端前端,适用于 JVM。 +- [`MiraiAndroid`](https://github.com/mzdluo123/MiraiAndroid): Android 应用前端。 + +**注意:`mirai-console` 1.0-RC 发布之前, 前端请使用 `mirai-console-pure` 而不是 `mirai-console-terminal`** + +## 选择版本 + +有关各类版本的区别,参考 [版本规范](Appendix.md#版本规范) + +[Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg? +[Bintray Download]: https://bintray.com/him188moe/mirai/mirai-console/ + +| 版本类型 | 版本号 | +|:------:|:------------:| +| 稳定 | - | +| 预览 | 1.0-M4 | +| 开发 | [![Version]][Bintray Download] | + +## 配置项目 + +### 使用模板项目 + +Mirai 鼓励插件开发者将自己的作品开源,并为此提供了模板项目。 + +注意,模板项目依赖的 Mirai Console 不一定是最新的。请检查 + +1. 访问 [mirai-console-plugin-template](https://github.com/project-mirai/mirai-console-plugin-template) +2. 点击绿色按钮 "Use this template",创建项目 +3. 克隆项目,检查并修改生成的属性 + +### 使用 Gradle 插件配置项目 + +`VERSION` 可在 + +若使用 `build.gradle.kts`: +```kotlin +plugins { + id("net.mamoe.mirai-console") version "VERSION" +} +``` + +若使用 `build.gradle`: +```groovy +plugins { + id 'net.mamoe.mirai-console' version 'VERSION' +} +``` + +完成。Mirai Console Gradle 插件会为你配置依赖等所有编译环境。 + +### 手动配置项目 + +添加依赖: +`build.gradle.kts`: +```kotlin +repositories { + jcenter() +} + +dependencies { + compileOnly("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API + compileOnly("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端 + + testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试 + testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试 +} +``` + +之后还需要配置 Kotlin `jvm-default` 编译参数,Kotlin 和 Java 的编译目标等。 +在打包插件时必须将依赖一并打包进插件 JAR,且排除 `mirai-core`,`mirai-console` 和它们的间接依赖,否则插件不会被加载。 \ No newline at end of file diff --git a/docs/Contributing.md b/docs/Contributing.md new file mode 100644 index 000000000..d90a8dd4c --- /dev/null +++ b/docs/Contributing.md @@ -0,0 +1,2 @@ +# Mirai Console - Contributing + diff --git a/docs/Preparations.md b/docs/Preparations.md new file mode 100644 index 000000000..3b3597637 --- /dev/null +++ b/docs/Preparations.md @@ -0,0 +1,103 @@ +# Mirai Console - Preparations + +***如果跳过本节内容,你很可能遇到无法解决的问题。*** + +### 环境要求 + +*不接受降低最低版本要求的建议* + +- JDK 11 +- Android:Android SDK 26+ (Android 8.0) +- Kotlin: 1.4 + +*Mirai Console 需要的 Kotlin 版本会与 Kotlin 最新稳定版本同步。* + +### 开发插件的准备工作 + +- 安装并配置 JDK 11 + +若使用 Java,或要修改 Mirai Console: + +- 使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) (或 `Android Studio`)。 +- IDE 需装有 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge) 插件 (先启动你的 IDE,再点击 [一键安装](https://plugins.jetbrains.com/embeddable/install/14816)) + +若使用 Kotlin,无特别要求。 + +## 前置知识 + +要学习为 mirai-console 开发原生支持的插件, 需要: + +- 掌握 Java 基础 +- 至少粗略了解 Kotlin 基础语法(30 分钟): + - [基本类型](https://www.kotlincn.net/docs/reference/basic-types.html) + - [类与继承](https://www.kotlincn.net/docs/reference/classes.html) + - [属性与字段](https://www.kotlincn.net/docs/reference/properties.html) + - [接口](https://www.kotlincn.net/docs/reference/interfaces.html) + - [扩展](https://www.kotlincn.net/docs/reference/extensions.html) + - [数据类](https://www.kotlincn.net/docs/reference/data-classes.html) + - [对象](https://www.kotlincn.net/docs/reference/object-declarations.html) + - [密封类](https://www.kotlincn.net/docs/reference/sealed-classes.html) + - **[Java 中调用 Kotlin](https://www.kotlincn.net/docs/reference/java-to-kotlin-interop.html)** +- 对于 Java 使用者,请阅读: + - [Java 用户的使用指南](#kotlin-源码阅读帮助) + - [在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数](#在-java-使用-mirai-console-中的-kotlin-suspend-函数) +- 对于 Kotlin 使用者,请熟知 [Kotlin `1.4` 版本带来的新特性](#mirai-console-使用的-kotlin-14-版本的新特性) + + +### Kotlin 源码阅读帮助 + +- Java 中的「方法」在 Kotlin 中均被称为「函数」。 +- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}` +- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)` + +### 在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数 + +#### 什么是 `suspend` 函数 + +`suspend` 函数中文是「挂起函数」,是 Kotlin 「[协程](https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html)」的一部分。 + +Kotlin 协程是语言级特性,函数的修饰符 `suspend` 会在编译阶段被处理。 + +对于一个挂起函数: +```kotlin +suspend fun test(): String +``` + +它会被编译为 `public Object test(Continuation $completion)`。 + +这是因为 Kotlin 对所有挂起函数都有这样的内部变化,并在编译时实现了协程的一些特性。 + +Java 用户无法调用这样的方法,因为 `Continuation` 的实现很复杂。 + +Mamoe 为此开发了 Kotlin 编译器插件 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge),通过 `@JvmBlockingBridge` 注解,在编译期额外生成一个供 Java 使用的方法,让 Java 用户可以使用拥有源码内相同的函数签名的方法。 + +要获取详细信息,参考 [Kotlin Jvm Blocking Bridge 编译器插件](https://github.com/mamoe/kotlin-jvm-blocking-bridge/blob/master/README-chs.md#%E7%BC%96%E8%AF%91%E5%99%A8%E6%8F%92%E4%BB%B6) + +### Mirai Console 使用的 Kotlin `1.4` 版本的新特性 + +在官方文档的 [语言特性与改进](https://www.kotlincn.net/docs/reference/whatsnew14.html#%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E4%B8%8E%E6%94%B9%E8%BF%9B) 基础上,Mirai Console 的一些设计基于 Kotlin 1.4 的更多新特性。 + +#### `object` 内的扩展函数的自动引用 +对于如下定义: +```kotlin +package org.example +object Obj { + fun String.foo() +} +``` +在 Kotlin `1.3`,要调用 `foo`,必须使用: +```kotlin +Obj.run { + "str".foo() +} +``` +因为 IDE 不会自动为 `String.foo` 添加 `import`。 + +Kotlin `1.4` 解决了这个问题。在使用 `"str".foo` 时 Kotlin 会自动添加 `org.example.Obj.foo` 的引用。 + +Mirai Console 很多单例对象都设计为 `interface + companion object INSTANCE` 的接口与实现模式,需要这样的新特性。例如: +```kotlin +interface MiraiConsole { + companion object INSTANCE : MiraiConsole by MiraiConsoleImpl // MiraiConsoleImpl 是内部实现,不公开 +} +``` diff --git a/docs/README.md b/docs/README.md index 6db0f09d9..95de05428 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,183 +1,3 @@ # Mirai Console 欢迎来到 mirai-console 开发文档! - -## 目录 - -- **[准备工作](#准备工作)** -- **[启动 Console](Run.md)** - -### 后端插件开发基础 - -- 插件 - [Plugin 模块](Plugins.md) -- 指令 - [Command 模块](Commands.md) -- 存储 - [PluginData 模块](PluginData.md) -- 权限 - [Permission 模块](Permissions.md) - -### 后端插件开发进阶 - -- 扩展 - [Extension 模块和扩展点](Extensions.md) - -### 实现前端 -- [FrontEnd](FrontEnd.md) - -[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt -[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt -[`PluginData`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt -[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt -[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt -[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt -[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt -[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt -[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt -[`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt - -## 准备工作 -***如果跳过本节内容,你很可能遇到无法解决的问题。*** - -### 环境要求 - -*不接受降低最低版本要求的建议* - -- JDK 11 -- Android:Android SDK 26+ (Android 8.0) -- Kotlin: 1.4 - -*Mirai Console 需要的 Kotlin 版本会与 Kotlin 最新稳定版本同步。* - -### 开发插件的准备工作 - -- 安装并配置 JDK 11 - -若使用 Java,或要修改 Mirai Console: - -- 使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) (或 `Android Studio`)。 -- IDE 需装有 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge) 插件 (先启动你的 IDE,再点击 [一键安装](https://plugins.jetbrains.com/embeddable/install/14816)) - -若使用 Kotlin,无特别要求。 - -### 前置知识 -要学习为 mirai-console 开发原生支持的插件, 需要: - -- 掌握 Java 基础 -- 至少粗略了解 Kotlin 基础语法(30 分钟): - - [基本类型](https://www.kotlincn.net/docs/reference/basic-types.html) - - [类与继承](https://www.kotlincn.net/docs/reference/classes.html) - - [属性与字段](https://www.kotlincn.net/docs/reference/properties.html) - - [接口](https://www.kotlincn.net/docs/reference/interfaces.html) - - [扩展](https://www.kotlincn.net/docs/reference/extensions.html) - - [数据类](https://www.kotlincn.net/docs/reference/data-classes.html) - - [对象](https://www.kotlincn.net/docs/reference/object-declarations.html) - - [密封类](https://www.kotlincn.net/docs/reference/sealed-classes.html) - - **[Java 中调用 Kotlin](https://www.kotlincn.net/docs/reference/java-to-kotlin-interop.html)** -- 对于 Java 使用者,请阅读 [Java 用户的使用指南](#java-用户的使用指南),[在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数](#在-java-使用-mirai-console-中的-kotlin-suspend-函数) -- 对于 Kotlin 使用者,请熟知 [Kotlin `1.4` 版本带来的新特性](#mirai-console-使用的-kotlin-14-版本的新特性) - - -## 附录 - -### Java 用户的使用指南 - -- Java 中的「方法」在 Kotlin 中均被称为「函数」。 -- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}` -- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)` - -### 在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数 - -#### 什么是 `suspend` 函数 - -`suspend` 函数中文是「挂起函数」,是 Kotlin 「[协程](https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html)」的一部分。 - -Kotlin 协程是语言级特性,函数的修饰符 `suspend` 会在编译阶段被处理。 - -对于一个挂起函数: -```kotlin -suspend fun test(): String -``` - -它会被编译为 `public Object test(Continuation $completion)`。 - -这是因为 Kotlin 对所有挂起函数都有这样的内部变化,并在编译时实现了协程的一些特性。 - -Java 用户无法调用这样的方法,因为 `Continuation` 的实现很复杂。 - -Mamoe 为此开发了 Kotlin 编译器插件 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge),通过 `@JvmBlockingBridge` 注解,在编译期额外生成一个供 Java 使用的方法,让 Java 用户可以使用拥有源码内相同的函数签名的方法。 - -要获取详细信息,参考 [Kotlin Jvm Blocking Bridge 编译器插件](https://github.com/mamoe/kotlin-jvm-blocking-bridge/blob/master/README-chs.md#%E7%BC%96%E8%AF%91%E5%99%A8%E6%8F%92%E4%BB%B6) - -### Mirai Console 使用的 Kotlin `1.4` 版本的新特性 - -在官方文档的 [语言特性与改进](https://www.kotlincn.net/docs/reference/whatsnew14.html#%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E4%B8%8E%E6%94%B9%E8%BF%9B) 基础上,Mirai Console 的一些设计基于 Kotlin 1.4 的更多新特性。 - -#### `object` 内的扩展函数的自动引用 -对于如下定义: -```kotlin -package org.example -object Obj { - fun String.foo() -} -``` -在 Kotlin `1.3`,要调用 `foo`,必须使用: -```kotlin -Obj.run { - "str".foo() -} -``` -因为 IDE 不会自动为 `String.foo` 添加 `import`。 - -Kotlin `1.4` 解决了这个问题。在使用 `"str".foo` 时 Kotlin 会自动添加 `org.example.Obj.foo` 的引用。 - -Mirai Console 很多单例对象都设计为 `interface + companion object INSTANCE` 的接口与实现模式,需要这样的新特性。例如: -```kotlin -interface MiraiConsole { - companion object INSTANCE : MiraiConsole by MiraiConsoleImpl // MiraiConsoleImpl 是内部实现,不公开 -} -``` - -#### Mirai Console 演进 - -Mirai Console 是不断前进的框架,将来必定会发生 API 弃用和重构。 -维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。 - -##### 版本规范 - -Mirai Console 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。 - -在日常开发中, Mirai Console 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。 - -在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。 -这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。 - -在大版本即将发布前,Mirai Console 会以 `-RC` 版本后缀发布最终的预览版本。 -`RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。 - -##### 版本选择 - -**稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。 - -| 目的 | 推荐至少更新到版本 | -|:--------------------------:|:--------------:| -| 生产环境 | `x.y.z` | -| 希望尽早体验稳定新特性的插件作者 | `-RC` | -| 无论如何都想体验新特性的插件作者 | `-M` | -| 前端实现者, 底层插件作者 | `-M` | -| 为 Mirai Console 提交 PR | `-dev` | - -其中,‘底层插件’ 表示提供扩展等的插件。如权限系统,其他语言插件加载器等。 - -##### 更新兼容性 - -对于 `x.y.z` 版本号: -- 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。 -- 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。 -- 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。 - -##### 弃用周期 - -一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。 - -如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。 -在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误); -在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。 - -`HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。 diff --git a/docs/Run.md b/docs/Run.md index 2cf39c5de..c4651e4bf 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -2,11 +2,11 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 -## 使用第三方工具自动启动 +## 使用第三方工具自动独立启动 https://github.com/LXY1226/MiraiOK -## 独立启动 +## 手动配置独立启动 ### 环境 - JRE 11+ / JDK 11+ diff --git a/tools/gradle-plugin/README.md b/tools/gradle-plugin/README.md index 78e0e7f29..dbe0dd749 100644 --- a/tools/gradle-plugin/README.md +++ b/tools/gradle-plugin/README.md @@ -1,2 +1,47 @@ -# mirai-console-gradle +# Mirai Console Gradle Plugin +Mirai Console Gradle 插件。 + +## 使用 + +参考 [ConfiguringProjects](../../docs/ConfiguringProjects.md#gradle)[ + +## 功能 + +- 为 `main` 源集配置 `mirai-core`,`mirai-console` 依赖 +- 为 `test` 源集配置 `mirai-core-qqandroied`, `mirai-console-terminal` 的依赖 (用于启动测试) +- 添加 mirai 依赖仓库链接 +- 配置插件 JAR 打包构建任务 `buildPlugin` (带依赖) + + +### `buildPlugin` + +用于打包插件和依赖为可以放入 Mirai Console `plugins` 目录加载的插件 JAR。 + +#### 执行 `buildPlugin` +```shell script +$ gradlew buildPlugin +``` + +打包结果存放在 `build/mirai/` 目录下。 + +## 配置 + +若要修改 Mirai Console Gradle 插件的默认配置,在 `build.gradle.kts` 或 `build.gradle` 内,使用 `mirai`: +```kotlin +mirai { // this: MiraiConsoleExtension + // 配置 +} +``` + +DSL 详见 [MiraiConsoleExtension](src/main/kotlin/net/mamoe/mirai/console/gradle/MiraiConsoleExtension.kt)。 + +#### 排除依赖 + +如果要在打包 JAR(`buildPlugin`)时排除一些依赖,请使用如下配置: + +```kotlin +mirai { + excludeDependency("com.google.code.gson", "gson") +} +``` \ No newline at end of file From 94398087ec5645e9029d1291383cab97242ef03a Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 19 Sep 2020 23:51:53 +0800 Subject: [PATCH 066/114] Update java dependency --- tools/intellij-plugin/build.gradle.kts | 3 ++- tools/intellij-plugin/src/main/resources/META-INF/plugin.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/intellij-plugin/build.gradle.kts b/tools/intellij-plugin/build.gradle.kts index d71fce91d..93a94e3f8 100644 --- a/tools/intellij-plugin/build.gradle.kts +++ b/tools/intellij-plugin/build.gradle.kts @@ -33,7 +33,8 @@ intellij { updateSinceUntilBuild = false setPlugins( - "org.jetbrains.kotlin:1.4.10-release-IJ2020.2-1@staging" + "org.jetbrains.kotlin:1.4.10-release-IJ2020.2-1@staging", + "java" ) } diff --git a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml index 935b52791..12267adbc 100644 --- a/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -9,6 +9,7 @@ Mamoe Technologies + com.intellij.modules.java com.intellij.modules.platform org.jetbrains.kotlin From e25c88aaec40577e3a755c8400350e0ed9fbd11b Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 00:04:24 +0800 Subject: [PATCH 067/114] Update docs --- docs/Preparations.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/Preparations.md b/docs/Preparations.md index 3b3597637..24dd2d120 100644 --- a/docs/Preparations.md +++ b/docs/Preparations.md @@ -2,26 +2,21 @@ ***如果跳过本节内容,你很可能遇到无法解决的问题。*** -### 环境要求 +***此文档假设你是 JVM 平台的开发者。若不是,请参考[其他语言 SDK](https://github.com/mamoe/mirai#%E5%BC%80%E5%8F%91%E8%80%85)*** -*不接受降低最低版本要求的建议* +### JVM 环境要求 -- JDK 11 +- 桌面 JVM:最低 Java 8,但推荐 Java 11 - Android:Android SDK 26+ (Android 8.0) -- Kotlin: 1.4 - -*Mirai Console 需要的 Kotlin 版本会与 Kotlin 最新稳定版本同步。* ### 开发插件的准备工作 -- 安装并配置 JDK 11 +#### 安装 IDE 插件 -若使用 Java,或要修改 Mirai Console: +推荐使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) 或 [Android Studio](https://developer.android.com/studio)。Mirai Console 提供 IntelliJ 插件来提升开发体验。 -- 使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) (或 `Android Studio`)。 -- IDE 需装有 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge) 插件 (先启动你的 IDE,再点击 [一键安装](https://plugins.jetbrains.com/embeddable/install/14816)) - -若使用 Kotlin,无特别要求。 +- [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge) ([JetBrains 插件仓库](https://plugins.jetbrains.com/plugin/14816-kotlin-jvm-blocking-bridge), [一键安装](https://plugins.jetbrains.com/embeddable/install/14816)):帮助 Java 用户调用 Kotlin suspend 函数 +- [Mirai Console IntelliJ](../tools/intellij-plugin/) ([JetBrains 插件仓库](https://plugins.jetbrains.com/plugin/15094-mirai-console), [一键安装](https://plugins.jetbrains.com/embeddable/install/15094)):提供错误检查等功能 ## 前置知识 From 128d83cb68a14f71fc0c301f0b129c764b270731 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 00:05:46 +0800 Subject: [PATCH 068/114] typo --- tools/gradle-plugin/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/gradle-plugin/README.md b/tools/gradle-plugin/README.md index dbe0dd749..679bb73d7 100644 --- a/tools/gradle-plugin/README.md +++ b/tools/gradle-plugin/README.md @@ -9,7 +9,7 @@ Mirai Console Gradle 插件。 ## 功能 - 为 `main` 源集配置 `mirai-core`,`mirai-console` 依赖 -- 为 `test` 源集配置 `mirai-core-qqandroied`, `mirai-console-terminal` 的依赖 (用于启动测试) +- 为 `test` 源集配置 `mirai-core-qqandroid`, `mirai-console-terminal` 的依赖 (用于启动测试) - 添加 mirai 依赖仓库链接 - 配置插件 JAR 打包构建任务 `buildPlugin` (带依赖) @@ -44,4 +44,4 @@ DSL 详见 [MiraiConsoleExtension](src/main/kotlin/net/mamoe/mirai/console/gradl mirai { excludeDependency("com.google.code.gson", "gson") } -``` \ No newline at end of file +``` From 100bc1040473f379559604c7865a2fd9bdce8fde Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 00:28:57 +0800 Subject: [PATCH 069/114] Fix shadow --- backend/mirai-console/build.gradle.kts | 50 ++++++------------- .../src/main/kotlin/dependencyExtensions.kt | 28 ++++++++++- .../mirai-console-terminal/build.gradle.kts | 28 ++--------- 3 files changed, 43 insertions(+), 63 deletions(-) diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 9d927a467..745d77d1a 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -56,50 +56,28 @@ kotlin { } dependencies { - implementation("net.mamoe:mirai-core:${Versions.core}") + compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}") + compileAndTestRuntime(kotlin("stdlib", Versions.kotlinStdlib)) + compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib)) - implementation(kotlinx("serialization-core", Versions.serialization)) - implementation(kotlin("reflect")) + compileAndTestRuntime("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}") + compileAndTestRuntime(kotlinx("coroutines-core", Versions.coroutines)) + compileAndTestRuntime(kotlinx("serialization-core", Versions.serialization)) + compileAndTestRuntime(kotlin("reflect")) - api("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") - implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}") - api("org.jetbrains:annotations:19.0.0") - api(kotlinx("coroutines-jdk8", Versions.coroutines)) + implementation("org.jetbrains:annotations:19.0.0") - api("com.vdurmont:semver4j:3.1.0") + smartApi(kotlinx("coroutines-jdk8", Versions.coroutines)) + smartApi("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") + smartApi("com.vdurmont:semver4j:3.1.0") - //api(kotlinx("collections-immutable", Versions.collectionsImmutable)) - - testApi(kotlinx("serialization-core", Versions.serialization)) testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(kotlin("stdlib-jdk8")) testApi(kotlin("test")) testApi(kotlin("test-junit5")) - testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0") + testApi("org.junit.jupiter:junit-jupiter-api:5.2.0") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0") - - -// val autoService = "1.0-rc7" -// kapt("com.google.auto.service", "auto-service", autoService) -// compileOnly("com.google.auto.service", "auto-service-annotations", autoService) -} - -ext.apply { - // 傻逼 compileAndRuntime 没 exclude 掉 - // 傻逼 gradle 第二次配置 task 会覆盖掉第一次的配置 - val x: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.() -> Unit = { - dependencyFilter.exclude { - when ("${it.moduleGroup}:${it.moduleName}") { - "net.mamoe:mirai-core" -> true - "org.jetbrains.kotlin:kotlin-stdlib" -> true - "org.jetbrains.kotlin:kotlin-stdlib-jdk8" -> true - "net.mamoe:mirai-core-qqandroid" -> true - else -> false - } - } - } - set("shadowJar", x) } tasks { @@ -120,14 +98,14 @@ tasks { Regex("""val buildDate: Instant = Instant.ofEpochSecond\(.*\)""") ) { """val buildDate: Instant = Instant.ofEpochSecond(${ - Instant.now().getEpochSecond() + Instant.now().epochSecond })""" } .replace( Regex("""val version: Semver = Semver\(".*", Semver.SemverType.LOOSE\)""") ) { """val version: Semver = Semver("${project.version}", Semver.SemverType.LOOSE)""" } ) - } + } } } } diff --git a/buildSrc/src/main/kotlin/dependencyExtensions.kt b/buildSrc/src/main/kotlin/dependencyExtensions.kt index c7fc138a5..d1b817404 100644 --- a/buildSrc/src/main/kotlin/dependencyExtensions.kt +++ b/buildSrc/src/main/kotlin/dependencyExtensions.kt @@ -7,8 +7,10 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +import org.gradle.api.artifacts.ExternalModuleDependency import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.kotlin.dsl.DependencyHandlerScope +import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo @Suppress("unused") fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" @@ -17,7 +19,29 @@ fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains fun DependencyHandlerScope.ktor(id: String, version: String = Versions.ktor) = "io.ktor:ktor-$id:$version" @Suppress("unused") -fun DependencyHandler.compileAndRuntime(any: Any) { +fun DependencyHandler.compileAndTestRuntime(any: Any) { add("compileOnly", any) - add("runtimeOnly", any) + add("testRuntimeOnly", any) +} + +fun DependencyHandler.smartApi( + dependencyNotation: String +): ExternalModuleDependency { + return addDependencyTo( + this, "api", dependencyNotation + ) { + fun exclude(group: String, module: String) { + exclude(mapOf( + "group" to group, + "module" to module + )) + } + exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8") + exclude("org.jetbrains.kotlin", "kotlin-stdlib") + exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") + exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core-common") + exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core") + exclude("org.jetbrains.kotlinx", "kotlinx-serialization-common") + exclude("org.jetbrains.kotlinx", "kotlinx-serialization-core") + } } diff --git a/frontend/mirai-console-terminal/build.gradle.kts b/frontend/mirai-console-terminal/build.gradle.kts index 2dc5414da..23d378113 100644 --- a/frontend/mirai-console-terminal/build.gradle.kts +++ b/frontend/mirai-console-terminal/build.gradle.kts @@ -36,34 +36,12 @@ dependencies { implementation("org.jline:jline:3.15.0") implementation("org.fusesource.jansi:jansi:1.18") - compileAndRuntime(project(":mirai-console")) - compileAndRuntime("net.mamoe:mirai-core:${Versions.core}") - compileAndRuntime(kotlin("stdlib", Versions.kotlinStdlib)) // embedded by core + compileAndTestRuntime(project(":mirai-console")) + compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}") + compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib)) // embedded by core - runtimeOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(project(":mirai-console")) - - -// val autoService = "1.0-rc7" -// kapt("com.google.auto.service", "auto-service", autoService) -// compileOnly("com.google.auto.service", "auto-service-annotations", autoService) -// testCompileOnly("com.google.auto.service", "auto-service-annotations", autoService) -} - -ext.apply { - // 傻逼 compileAndRuntime 没 exclude 掉 - // 傻逼 gradle 第二次配置 task 会覆盖掉第一次的配置 - val x: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.() -> Unit = { - dependencyFilter.include { - when ("${it.moduleGroup}:${it.moduleName}") { - "org.jline:jline" -> true - "org.fusesource.jansi:jansi" -> true - else -> false - } - } - } - this.set("shadowJar", x) } version = Versions.consoleTerminal From ef4d259f601ac9669513a32a192945f445cbc93d Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 13:55:00 +0800 Subject: [PATCH 070/114] Document improvements --- .../mamoe/mirai/console/util/SemVersion.kt | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 783becc0c..f07b8d5b9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -12,6 +12,8 @@ * @author Karlatemp */ +@file:Suppress("unused") + package net.mamoe.mirai.console.util import kotlinx.serialization.KSerializer @@ -21,20 +23,14 @@ import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals +import net.mamoe.mirai.console.util.SemVersion.RangeRequirement /** - * 语义化版本支持 + * [语义化版本](https://semver.org/lang/zh-CN/) 支持 * - * 在阅读此文件前, 请先阅读 https://semver.org/lang/zh-CN/ - * 该文档说明了语义化版本是什么, 此文件不再过多描述 + * 解析示例: * - * ---- - * - * 这是一个例子 `1.0.0-M4+c25733b8` - * - * 将会解析出三个内容, mainVersion(核心版本号), identifier(先行版本号) 和 metadata(元数据). - * - * 对这个例子进行解析会得到 + * `1.0.0-M4+c25733b8` 将会解析出三个内容, mainVersion (核心版本号), [identifier] (先行版本号) 和 [metadata] (元数据). * ``` * SemVersion( * mainVersion = IntArray [1, 0, 0], @@ -43,9 +39,10 @@ import net.mamoe.mirai.console.util.SemVersion.Companion.equals * ) * ``` * 其中 identifier 和 metadata 都是可选的. - * 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在. - * 但是不允许 0.0.0.0 之类的存在 * + * 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在. + * + * @see RangeRequirement */ @Serializable(with = SemVersion.SemVersionAsStringSerializer::class) public data class SemVersion internal constructor( @@ -83,7 +80,7 @@ public data class SemVersion internal constructor( * 解析一个版本号, 将会返回一个 [SemVersion], * 如果发生解析错误将会抛出一个 [IllegalArgumentException] 或者 [NumberFormatException] * - * 对于版本号的组成, 我们有以下规定: + * 对于版本号的组成, 有以下规定: * - 必须包含主版本号和次版本号 * - 存在 先行版本号 的时候 先行版本号 不能为空 * - 存在 元数据 的时候 元数据 不能为空 @@ -104,7 +101,7 @@ public data class SemVersion internal constructor( if (!SEM_VERSION_REGEX.matches(version)) { throw IllegalArgumentException("`$version` not a valid version") } - var mainVersionEnd: Int = 0 + var mainVersionEnd = 0 kotlin.run { val iterator = version.iterator() while (iterator.hasNext()) { @@ -154,7 +151,7 @@ public data class SemVersion internal constructor( * - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC * - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC * - * 对于多个规则, 也允许使用 `||` 拼接在一起. + * 对于多个规则, 也允许使用 `||` 拼接. * 例如: * - `1.x || 2.x || 3.0.0` * - `<= 0.5.3 || >= 1.0.0` From 798425e6e8b0f89b374dcda238893f6cb70878f4 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 13:55:44 +0800 Subject: [PATCH 071/114] Rename SemVersion.RangeRequirement.check to test --- .../console/internal/util/SemVersionInternal.kt | 6 +++--- .../net/mamoe/mirai/console/util/SemVersion.kt | 12 ++++++------ .../net/mamoe/mirai/console/util/TestSemVersion.kt | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index f880a8ecd..718a12d09 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -91,8 +91,8 @@ internal object SemVersionInternal { private fun SemVersion.RangeRequirement.withRule(rule: String): SemVersion.RangeRequirement { return object : SemVersion.RangeRequirement { - override fun check(version: SemVersion): Boolean { - return this@withRule.check(version) + override fun test(version: SemVersion): Boolean { + return this@withRule.test(version) } override fun toString(): String { @@ -112,7 +112,7 @@ internal object SemVersionInternal { if (checks.size == 1) return checks[0] SemVersion.RangeRequirement { checks.forEach { rule -> - if (rule.check(it)) return@RangeRequirement true + if (rule.test(it)) return@RangeRequirement true } return@RangeRequirement false }.withRule(requirement) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 783becc0c..713e9dee4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -62,7 +62,7 @@ public data class SemVersion internal constructor( */ public fun interface RangeRequirement { /** 在 [version] 满足此要求时返回 true */ - public fun check(version: SemVersion): Boolean + public fun test(version: SemVersion): Boolean } public object SemVersionAsStringSerializer : KSerializer by String.serializer().map( @@ -169,25 +169,25 @@ public data class SemVersion internal constructor( return SemVersionInternal.parseRangeRequirement(requirement) } - /** @see [RangeRequirement.check] */ + /** @see [RangeRequirement.test] */ @JvmStatic - public fun RangeRequirement.check(version: String): Boolean = check(parse(version)) + public fun RangeRequirement.test(version: String): Boolean = test(parse(version)) /** * 当满足 [requirement] 时返回 true, 否则返回 false */ @JvmStatic - public fun SemVersion.satisfies(requirement: RangeRequirement): Boolean = requirement.check(this) + public fun SemVersion.satisfies(requirement: RangeRequirement): Boolean = requirement.test(this) /** for Kotlin only */ @JvmStatic @JvmSynthetic - public operator fun RangeRequirement.contains(version: SemVersion): Boolean = check(version) + public operator fun RangeRequirement.contains(version: SemVersion): Boolean = test(version) /** for Kotlin only */ @JvmStatic @JvmSynthetic - public operator fun RangeRequirement.contains(version: String): Boolean = check(version) + public operator fun RangeRequirement.contains(version: String): Boolean = test(version) } @Transient diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index 56d34ab71..b76d610e1 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai.console.util -import net.mamoe.mirai.console.util.SemVersion.Companion.check +import net.mamoe.mirai.console.util.SemVersion.Companion.test import org.junit.jupiter.api.Test internal class TestSemVersion { @@ -44,12 +44,12 @@ internal class TestSemVersion { @Test internal fun testRequirement() { fun SemVersion.RangeRequirement.assert(version: String): SemVersion.RangeRequirement { - assert(check(version)) { version } + assert(test(version)) { version } return this } fun SemVersion.RangeRequirement.assertFalse(version: String): SemVersion.RangeRequirement { - assert(!check(version)) { version } + assert(!test(version)) { version } return this } SemVersion.parseRangeRequirement("1.0") From 7a533d4667dd0a0b5babf1debf7e2f37c9537bf0 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 13:56:12 +0800 Subject: [PATCH 072/114] Use lazy for SemVersion.toString --- .../mamoe/mirai/console/util/SemVersion.kt | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index f07b8d5b9..939fe7ea6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -188,23 +188,22 @@ public data class SemVersion internal constructor( } @Transient - private var toString: String? = null // For cache. - override fun toString(): String { - return toString ?: kotlin.run { - buildString { - mainVersion.joinTo(this, ".") - identifier?.let { identifier -> - append('-') - append(identifier) - } - metadata?.let { metadata -> - append('+') - append(metadata) - } - }.also { toString = it } + private val toString: String by lazy(LazyThreadSafetyMode.NONE) { + buildString { + mainVersion.joinTo(this, ".") + identifier?.let { identifier -> + append('-') + append(identifier) + } + metadata?.let { metadata -> + append('+') + append(metadata) + } } } + override fun toString(): String = toString + /** * 将 [SemVersion] 转为 Kotlin data class 风格的 [String] */ From 9c2a0abda55f856d2d34e18157404b60e2850984 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 14:09:48 +0800 Subject: [PATCH 073/114] Move implementations to SemVersionInternal --- .../internal/util/SemVersionInternal.kt | 49 +++++++++++++++++ .../mamoe/mirai/console/util/SemVersion.kt | 52 +------------------ 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index f880a8ecd..6d2da7ae5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -32,6 +32,55 @@ internal object SemVersionInternal { } } + 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() + + /** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */ + @JvmStatic + private fun String.parseMainVersion(): IntArray = + split('.').map { it.toInt() }.toIntArray() + + + fun parse(version: String): SemVersion { + if (!SEM_VERSION_REGEX.matches(version)) { + throw IllegalArgumentException("`$version` not a valid version") + } + var mainVersionEnd = 0 + kotlin.run { + val iterator = version.iterator() + while (iterator.hasNext()) { + val next = iterator.next() + if (next == '-' || next == '+') { + break + } + mainVersionEnd++ + } + } + var identifier: String? = null + var metadata: String? = null + if (mainVersionEnd != version.length) { + when (version[mainVersionEnd]) { + '-' -> { + val metadataSplitter = version.indexOf('+', startIndex = mainVersionEnd) + if (metadataSplitter == -1) { + identifier = version.substring(mainVersionEnd + 1) + } else { + identifier = version.substring(mainVersionEnd + 1, metadataSplitter) + metadata = version.substring(metadataSplitter + 1) + } + } + '+' -> { + metadata = version.substring(mainVersionEnd + 1) + } + } + } + return SemVersion( + mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(), + identifier = identifier, + metadata = metadata + ) + } + @JvmStatic private fun String.parseRule(): SemVersion.RangeRequirement { val trimmed = trim() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 939fe7ea6..cc75c416e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -68,14 +68,6 @@ public data class SemVersion internal constructor( ) public companion object { - 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() - - /** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */ - @JvmStatic - private fun String.parseMainVersion(): IntArray = - split('.').map { it.toInt() }.toIntArray() - /** * 解析一个版本号, 将会返回一个 [SemVersion], * 如果发生解析错误将会抛出一个 [IllegalArgumentException] 或者 [NumberFormatException] @@ -97,45 +89,7 @@ public data class SemVersion internal constructor( */ @Throws(IllegalArgumentException::class, NumberFormatException::class) @JvmStatic - public fun parse(version: String): SemVersion { - if (!SEM_VERSION_REGEX.matches(version)) { - throw IllegalArgumentException("`$version` not a valid version") - } - var mainVersionEnd = 0 - kotlin.run { - val iterator = version.iterator() - while (iterator.hasNext()) { - val next = iterator.next() - if (next == '-' || next == '+') { - break - } - mainVersionEnd++ - } - } - var identifier: String? = null - var metadata: String? = null - if (mainVersionEnd != version.length) { - when (version[mainVersionEnd]) { - '-' -> { - val metadataSplitter = version.indexOf('+', startIndex = mainVersionEnd) - if (metadataSplitter == -1) { - identifier = version.substring(mainVersionEnd + 1) - } else { - identifier = version.substring(mainVersionEnd + 1, metadataSplitter) - metadata = version.substring(metadataSplitter + 1) - } - } - '+' -> { - metadata = version.substring(mainVersionEnd + 1) - } - } - } - return SemVersion( - mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(), - identifier = identifier, - metadata = metadata - ) - } + public fun parse(version: String): SemVersion = SemVersionInternal.parse(version) /** * 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException] @@ -162,9 +116,7 @@ public data class SemVersion internal constructor( */ @Throws(IllegalArgumentException::class) @JvmStatic - public fun parseRangeRequirement(requirement: String): RangeRequirement { - return SemVersionInternal.parseRangeRequirement(requirement) - } + public fun parseRangeRequirement(requirement: String): RangeRequirement = SemVersionInternal.parseRangeRequirement(requirement) /** @see [RangeRequirement.check] */ @JvmStatic From 44598aa1fae78aa6acbd55ed54d36fb0c969dae9 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 14:10:08 +0800 Subject: [PATCH 074/114] Convert receiver to parameter --- .../mirai/console/internal/util/SemVersionInternal.kt | 10 +++++----- .../kotlin/net/mamoe/mirai/console/util/SemVersion.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index 718a12d09..02b365999 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -120,23 +120,23 @@ internal object SemVersionInternal { } @JvmStatic - fun SemVersion.compareInternal(other: SemVersion): Int { + fun compareInternal(source: SemVersion, other: SemVersion): Int { // ignored metadata in comparing // If $this equals $other (without metadata), // return same. - if (other.mainVersion.contentEquals(mainVersion) && identifier == other.identifier) { + if (other.mainVersion.contentEquals(source.mainVersion) && source.identifier == other.identifier) { return 0 } fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 } // Compare main-version - for (index in 0 until (max(mainVersion.size, other.mainVersion.size))) { - val result = mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index)) + for (index in 0 until (max(source.mainVersion.size, other.mainVersion.size))) { + val result = source.mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index)) if (result != 0) return result } // If main-versions are same. - var identifier0 = identifier + var identifier0 = source.identifier var identifier1 = other.identifier // If anyone doesn't have the identifier... if (identifier0 == null || identifier1 == null) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 77cd0746a..2610cfb6e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -233,6 +233,6 @@ public data class SemVersion internal constructor( * if it's greater than [other]. */ public override operator fun compareTo(other: SemVersion): Int { - return SemVersionInternal.run { compareInternal(other) } + return SemVersionInternal.run { compareInternal(this@SemVersion, other) } } } From 30a10f56d0cdbcbb9d9e49ba286a629e3a310be2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 14:25:54 +0800 Subject: [PATCH 075/114] Update docs --- backend/codegen/README.md | 6 +++++ ...ngCodegen.kt => ValuePluginDataCodegen.kt} | 0 backend/mirai-console/README.md | 3 +++ backend/mirai-console/build.gradle.kts | 1 - docs/Contributing.md | 23 +++++++++++++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 backend/codegen/README.md rename backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/{ValueSettingCodegen.kt => ValuePluginDataCodegen.kt} (100%) create mode 100644 backend/mirai-console/README.md diff --git a/backend/codegen/README.md b/backend/codegen/README.md new file mode 100644 index 000000000..013c05c7b --- /dev/null +++ b/backend/codegen/README.md @@ -0,0 +1,6 @@ +# Mirai Console - Backend.codegen + +后端代码生成模块,用于最小化重复代码的人工成本。 + +- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/main/kotlin/net/mamoe/mirai/console/codegen/MessageScopeCodegen.kt#L33) +- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18) diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueSettingCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt similarity index 100% rename from backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueSettingCodegen.kt rename to backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt diff --git a/backend/mirai-console/README.md b/backend/mirai-console/README.md new file mode 100644 index 000000000..451b5aaee --- /dev/null +++ b/backend/mirai-console/README.md @@ -0,0 +1,3 @@ +# Mirai Console - Backend + +Mirai Console 后端模块. 发布为 `net.mamoe:mirai-console`. \ No newline at end of file diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 745d77d1a..d520dd69e 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -69,7 +69,6 @@ dependencies { smartApi(kotlinx("coroutines-jdk8", Versions.coroutines)) smartApi("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") - smartApi("com.vdurmont:semver4j:3.1.0") testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(kotlin("stdlib-jdk8")) diff --git a/docs/Contributing.md b/docs/Contributing.md index d90a8dd4c..a562a15d4 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -1,2 +1,25 @@ # Mirai Console - Contributing +感谢你来到这里,感谢你对 Mirai Console 做的一切贡献。 + +## 开发 Mirai Console + +### 模块 + +Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,Intellij 插件。 + +``` +/ +|--- backend 后端 +| |--- codegen 后端代码生成工具 +| `--- mirai-console 后端主模块, 发布为 net.mamoe:mirai-console +|--- buildSrc 项目构建 +|--- frontend 前端 +| `--- mirai-console-terminal 终端前端,发布为 net.mamoe:mirai-console-terminal +`--- tools 开发工具 + |--- compiler-common 编译器通用模块 + |--- gradle-plugin Gradle 插件,发布为 net.mamoe.mirai-console + `--- intellij-plugin IntelliJ 平台 IDE 插件,发布为 Mirai Console +``` + +请前往各模块内的 README.md 查看详细说明。 \ No newline at end of file From 6912982949765dcbfaaac3fcba4d36506ac6ac30 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 14:31:27 +0800 Subject: [PATCH 076/114] Fix docs --- .../net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt | 2 +- .../net/mamoe/mirai/console/plugin/loader/PluginLoader.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt index 66986ee9e..30b4e7f34 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt @@ -15,7 +15,7 @@ import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl import net.mamoe.mirai.console.plugin.loader.FilePluginLoader /** - * 内建的 Jar (JVM) 插件加载器 + * JVM 插件加载器 */ public interface JvmPluginLoader : CoroutineScope, FilePluginLoader { /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt index 04cae8515..280ce144b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt @@ -61,10 +61,9 @@ public interface PluginLoader

{ * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等). * * @see PluginDescription 插件描述 - * @see getPluginDescription 无 receiver, 接受参数的版本. */ @Throws(PluginLoadException::class) - public fun getPluginDescription(plugin: P): D // Java signature: `public D getDescription(P)` + public fun getPluginDescription(plugin: P): D /** * 主动加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例 From a6411005f59f0b76d7bab789d5050669f85b1fe3 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 15:25:27 +0800 Subject: [PATCH 077/114] Update docs --- docs/Contributing.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Contributing.md b/docs/Contributing.md index a562a15d4..f4e4befbb 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -22,4 +22,11 @@ Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,In `--- intellij-plugin IntelliJ 平台 IDE 插件,发布为 Mirai Console ``` -请前往各模块内的 README.md 查看详细说明。 \ No newline at end of file +请前往各模块内的 README.md 查看详细说明。 + +### 构建 +```shell script +gradlew build +``` + +首次加载和构建 mirai-console 项目可能要花费数小时时间。 \ No newline at end of file From eb7cd3811db83f9aae48083738eaa4ef2a3e7e93 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 18:01:02 +0800 Subject: [PATCH 078/114] Deploy SemVersion --- backend/mirai-console/build.gradle.kts | 4 +- .../net/mamoe/mirai/console/MiraiConsole.kt | 4 +- .../MiraiConsoleFrontEndDescription.kt | 7 +- .../internal/MiraiConsoleBuildConstants.kt | 6 +- .../MiraiConsoleImplementationBridge.kt | 4 +- .../internal/plugin/PluginManagerImpl.kt | 1 + .../net/mamoe/mirai/console/plugin/Plugin.kt | 4 +- .../plugin/description/PluginDependency.kt | 4 +- .../plugin/description/PluginDescription.kt | 4 +- .../plugin/description/VersionRequirement.kt | 242 ------------------ .../plugin/jvm/JvmPluginDescription.kt | 34 ++- .../mamoe/mirai/console/util/SemVersion.kt | 3 +- .../util/SemVersionRangeRequirementBuilder.kt | 90 +++++++ .../mamoe/mirai/console/TestMiraiConosle.kt | 6 +- .../MiraiConsoleImplementationTerminal.kt | 8 +- 15 files changed, 133 insertions(+), 288 deletions(-) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index d520dd69e..6aad5bba4 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -101,8 +101,8 @@ tasks { })""" } .replace( - Regex("""val version: Semver = Semver\(".*", Semver.SemverType.LOOSE\)""") - ) { """val version: Semver = Semver("${project.version}", Semver.SemverType.LOOSE)""" } + Regex("""val version: SemVersion = SemVersion.parse\(".*"\)""") + ) { """val version: SemVersion = SemVersion.parse("${project.version}")""" } ) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 15f4b9a01..42106685e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.console -import com.vdurmont.semver4j.Semver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.Bot @@ -29,6 +28,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext +import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiLogger import java.io.File @@ -79,7 +79,7 @@ public interface MiraiConsole : CoroutineScope { /** * 此 Console 后端版本号 */ - public val version: Semver + public val version: SemVersion @ConsoleExperimentalApi diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt index 21c1f8103..6b7426d4d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt @@ -9,7 +9,8 @@ package net.mamoe.mirai.console -import com.vdurmont.semver4j.Semver +import net.mamoe.mirai.console.util.SemVersion + /** * 有关前端实现的信息 @@ -28,7 +29,7 @@ public interface MiraiConsoleFrontEndDescription { /** * 此前端实现的名称 */ - public val version: Semver + public val version: SemVersion /** * 兼容的 [MiraiConsole] 后端版本号 @@ -37,7 +38,7 @@ public interface MiraiConsoleFrontEndDescription { * * 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性. */ - public val compatibleBackendVersion: Semver? get() = null + public val compatibleBackendVersion: SemVersion? get() = null /** * 返回显示在 [MiraiConsole] 启动时的信息 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index 19dd40076..f1889aeab 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -9,13 +9,13 @@ package net.mamoe.mirai.console.internal -import com.vdurmont.semver4j.Semver +import net.mamoe.mirai.console.util.SemVersion import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1600522812) + val buildDate: Instant = Instant.ofEpochSecond(1600596035) @JvmStatic - val version: Semver = Semver("1.0-RC-dev-28", Semver.SemverType.LOOSE) + val version: SemVersion = SemVersion.parse("1.0-RC-dev-28") } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index 0751b2432..175af2331 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.console.internal -import com.vdurmont.semver4j.Semver import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -47,6 +46,7 @@ import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInput +import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.* import java.nio.file.Path import java.time.Instant @@ -66,7 +66,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate - override val version: Semver by MiraiConsoleBuildConstants::version + override val version: SemVersion by MiraiConsoleBuildConstants::version override val rootPath: Path by instance::rootPath override val frontEndDescription: MiraiConsoleFrontEndDescription by instance::frontEndDescription diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index f53ebac6f..2f15ba42a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -27,6 +27,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoadException import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope +import net.mamoe.mirai.console.util.SemVersion.Companion.contains import net.mamoe.mirai.utils.info import java.io.File import java.nio.file.Path diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt index 55a00f667..8db84c890 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.console.plugin -import com.vdurmont.semver4j.Semver import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable @@ -20,6 +19,7 @@ import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.loader.PluginLoader +import net.mamoe.mirai.console.util.SemVersion /** * 表示一个 mirai-console 插件. @@ -62,7 +62,7 @@ public inline val Plugin.name: String get() = this.description.name /** * 获取 [PluginDescription.version] */ -public inline val Plugin.version: Semver get() = this.description.version +public inline val Plugin.version: SemVersion get() = this.description.version /** * 获取 [PluginDescription.info] diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt index 49bfad6ff..d22883093 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt @@ -11,6 +11,8 @@ package net.mamoe.mirai.console.plugin.description +import net.mamoe.mirai.console.util.SemVersion + /** * 插件的一个依赖的信息. * @@ -29,7 +31,7 @@ public data class PluginDependency @JvmOverloads constructor( * ### 示例 * `Requirement.buildIvy("[1.0, 2.0)")` */ - public val versionRequirement: VersionRequirement? = null, + public val versionRequirement: SemVersion.RangeRequirement? = null, /** * 若为 `false`, 插件在找不到此依赖时也能正常加载. */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt index 85c199628..ca9267495 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt @@ -9,10 +9,10 @@ package net.mamoe.mirai.console.plugin.description -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.util.SemVersion /** @@ -93,7 +93,7 @@ public interface PluginDescription { * @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本. */ @ResolveContext(PLUGIN_VERSION) - public val version: Semver + public val version: SemVersion /** * 插件信息, 允许为空 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt deleted file mode 100644 index e3a3e6680..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/VersionRequirement.kt +++ /dev/null @@ -1,242 +0,0 @@ -package net.mamoe.mirai.console.plugin.description - -import com.vdurmont.semver4j.Requirement -import com.vdurmont.semver4j.Semver - -public sealed class VersionRequirement { - public abstract operator fun contains(version: Semver): Boolean - public fun contains(version: String): Boolean = contains(Semver(version, Semver.SemverType.LOOSE)) - - public class Exact - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - constructor( - version: Semver, - ) : VersionRequirement() { - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - public val version: Semver = version.toStrict() - - @Suppress("DEPRECATION_ERROR") - public constructor(version: String) : this(Semver(version, Semver.SemverType.LOOSE)) - - @Suppress("DEPRECATION_ERROR") - override fun contains(version: Semver): Boolean = this.version.isEquivalentTo(version.toStrict()) - } - - public data class MatchesNpmPattern( - val pattern: String, - ) : VersionRequirement() { - private val requirement = Requirement.buildNPM(pattern) - override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict()) - } - - public data class MatchesIvyPattern( - val pattern: String, - ) : VersionRequirement() { - private val requirement = Requirement.buildIvy(pattern) - override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict()) - } - - - public data class MatchesCocoapodsPattern( - val pattern: String, - ) : VersionRequirement() { - private val requirement = Requirement.buildCocoapods(pattern) - override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict()) - } - - public abstract class Custom : VersionRequirement() - - @Suppress("MemberVisibilityCanBePrivate") - public class InRange( - begin: Semver, - public val beginInclusive: Boolean, - end: Semver, - public val endInclusive: Boolean, - ) : VersionRequirement() { - public val end: Semver = end.toStrict() - public val begin: Semver = begin.toStrict() - - public constructor( - begin: String, - beginInclusive: Boolean, - end: Semver, - endInclusive: Boolean, - ) : this(Semver(begin, Semver.SemverType.LOOSE), beginInclusive, end, endInclusive) - - public constructor( - begin: String, - beginInclusive: Boolean, - end: String, - endInclusive: Boolean, - ) : this(Semver(begin, Semver.SemverType.LOOSE), - beginInclusive, - Semver(end, Semver.SemverType.LOOSE), - endInclusive) - - public constructor( - begin: Semver, - beginInclusive: Boolean, - end: String, - endInclusive: Boolean, - ) : this(begin, beginInclusive, Semver(end, Semver.SemverType.LOOSE), endInclusive) - - override fun contains(version: Semver): Boolean { - val strict = version.toStrict() - return if (beginInclusive) { - strict.isGreaterThanOrEqualTo(begin) - } else { - strict.isGreaterThan(begin) - } && if (endInclusive) { - strict.isLowerThanOrEqualTo(end) - } else { - strict.isLowerThan(end) - } - } - - override fun toString(): String { - return buildString { - append(if (beginInclusive) "[" else "(") - append(begin) - append(",") - append(end) - append(if (endInclusive) "]" else ")") - } - } - } - - - @Suppress("unused", "DeprecatedCallableAddReplaceWith") - public class Builder { - @Suppress("DEPRECATION_ERROR") - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public fun exact(version: Semver): VersionRequirement = Exact(version) - - @ILoveKafuuChinoForever - public fun exact(version: String): VersionRequirement = Exact(version) - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public fun custom(checker: (version: Semver) -> Boolean): VersionRequirement { - return object : Custom() { - override fun contains(version: Semver): Boolean = checker(version) - } - } - - /** - * @see Semver.SemverType.NPM - */ - @ILoveKafuuChinoForever - public fun npmPattern(versionPattern: String): VersionRequirement { - return MatchesNpmPattern(versionPattern) - } - - /** - * @see Semver.SemverType.IVY - */ - @ILoveKafuuChinoForever - public fun ivyPattern(versionPattern: String): VersionRequirement { - return MatchesIvyPattern(versionPattern) - } - - /** - * @see Semver.SemverType.COCOAPODS - */ - @ILoveKafuuChinoForever - public fun cocoapodsPattern(versionPattern: String): VersionRequirement { - return MatchesCocoapodsPattern(versionPattern) - } - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public fun range( - begin: Semver, - beginInclusive: Boolean, - end: Semver, - endInclusive: Boolean, - ): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive) - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public fun range( - begin: String, - beginInclusive: Boolean, - end: Semver, - endInclusive: Boolean, - ): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive) - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public fun range( - begin: Semver, - beginInclusive: Boolean, - end: String, - endInclusive: Boolean, - ): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive) - - @ILoveKafuuChinoForever - public fun range( - begin: String, - beginInclusive: Boolean, - end: String, - endInclusive: Boolean, - ): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive) - - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public operator fun Semver.rangeTo(endInclusive: Semver): VersionRequirement { - return InRange(this, true, endInclusive, true) - } - - @ILoveKafuuChinoForever - public operator fun Semver.rangeTo(endInclusive: String): VersionRequirement { - return InRange(this, true, Semver(endInclusive, Semver.SemverType.LOOSE), true) - } - - @ILoveKafuuChinoForever - public operator fun String.rangeTo(endInclusive: String): VersionRequirement { - return InRange(Semver(this, Semver.SemverType.LOOSE), - true, - Semver(endInclusive, Semver.SemverType.LOOSE), - true) - } - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public operator fun String.rangeTo(endInclusive: Semver): VersionRequirement { - return InRange(Semver(this, Semver.SemverType.LOOSE), true, endInclusive, true) - } - - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public infix fun Semver.until(endExclusive: Semver): VersionRequirement { - return InRange(this, true, endExclusive, false) - } - - @ILoveKafuuChinoForever - public infix fun Semver.until(endExclusive: String): VersionRequirement { - return InRange(this, true, Semver(endExclusive, Semver.SemverType.LOOSE), false) - } - - @ILoveKafuuChinoForever - public infix fun String.until(endExclusive: String): VersionRequirement { - return InRange(Semver(this, Semver.SemverType.LOOSE), - true, - Semver(endExclusive, Semver.SemverType.LOOSE), - false) - } - - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) - @ILoveKafuuChinoForever - public infix fun String.until(endExclusive: Semver): VersionRequirement { - return InRange(Semver(this, Semver.SemverType.LOOSE), true, endExclusive, false) - } - - @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.BINARY) - @DslMarker - internal annotation class ILoveKafuuChinoForever - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 0b61101bc..242249f21 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -11,12 +11,12 @@ package net.mamoe.mirai.console.plugin.jvm -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.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription -import net.mamoe.mirai.console.plugin.description.VersionRequirement +import net.mamoe.mirai.console.util.SemVersion +import net.mamoe.mirai.console.util.SemVersionRangeRequirementBuilder /** * JVM 插件的描述. 通常作为 `plugin.yml` @@ -66,7 +66,7 @@ public interface JvmPluginDescription : PluginDescription { /** * @see [PluginDescription.version] */ - @ResolveContext(PLUGIN_VERSION) version: Semver, + @ResolveContext(PLUGIN_VERSION) version: SemVersion, /** * @see [PluginDescription.name] */ @@ -97,17 +97,15 @@ public interface JvmPluginDescription : PluginDescription { * * @see [JvmPluginDescription.invoke] */ -public class JvmPluginDescriptionBuilder -@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) -constructor( +public class JvmPluginDescriptionBuilder( private var id: String, - private var version: Semver, + private var version: SemVersion, ) { @Suppress("DEPRECATION_ERROR") public constructor( @ResolveContext(PLUGIN_NAME) id: String, @ResolveContext(PLUGIN_VERSION) version: String, - ) : this(id, Semver(version, Semver.SemverType.LOOSE)) + ) : this(id, SemVersion.parse(version)) private var name: String = id private var author: String = "" @@ -118,14 +116,12 @@ constructor( public fun name(@ResolveContext(PLUGIN_NAME) value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() } - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) @ILoveKuriyamaMiraiForever public fun version(@ResolveContext(PLUGIN_VERSION) value: String): JvmPluginDescriptionBuilder = - apply { this.version = Semver(value, Semver.SemverType.LOOSE) } + apply { this.version = SemVersion.parse(value) } - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) @ILoveKuriyamaMiraiForever - public fun version(@ResolveContext(PLUGIN_VERSION) value: Semver): JvmPluginDescriptionBuilder = + public fun version(@ResolveContext(PLUGIN_VERSION) value: SemVersion): JvmPluginDescriptionBuilder = apply { this.version = value } @ILoveKuriyamaMiraiForever @@ -161,7 +157,7 @@ constructor( public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, - versionRequirement: VersionRequirement, + versionRequirement: SemVersion.RangeRequirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional)) } @@ -174,7 +170,7 @@ constructor( @ILoveKuriyamaMiraiForever public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, - versionRequirement: VersionRequirement, + versionRequirement: SemVersion.RangeRequirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, versionRequirement, false)) } @@ -203,17 +199,17 @@ constructor( * ``` * * @see PluginDependency - * @see VersionRequirement.Builder + * @see SemVersionRangeRequirementBuilder */ @ILoveKuriyamaMiraiForever public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, - versionRequirement: VersionRequirement.Builder.() -> VersionRequirement, + versionRequirement: SemVersionRangeRequirementBuilder.() -> SemVersion.RangeRequirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, - VersionRequirement.Builder().run(versionRequirement), + SemVersionRangeRequirementBuilder.run(versionRequirement), isOptional)) } @@ -259,7 +255,7 @@ public data class SimpleJvmPluginDescription ) @JvmOverloads public constructor( public override val name: String, - public override val version: Semver, + public override val version: SemVersion, public override val id: String = name, public override val author: String = "", public override val info: String = "", @@ -285,7 +281,7 @@ public data class SimpleJvmPluginDescription author: String = "", info: String = "", dependencies: Set = setOf(), - ) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies) + ) : this(name, SemVersion.parse(version), id, author, info, dependencies) init { require(!name.contains(':')) { "':' is forbidden in plugin name" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 56e7ac81e..38903ccbd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -20,6 +20,7 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals @@ -89,7 +90,7 @@ public data class SemVersion internal constructor( */ @Throws(IllegalArgumentException::class, NumberFormatException::class) @JvmStatic - public fun parse(version: String): SemVersion = SemVersionInternal.parse(version) + public fun parse(@ResolveContext(ResolveContext.Kind.PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) /** * 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException] diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt new file mode 100644 index 000000000..ec4d663a4 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt @@ -0,0 +1,90 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +package net.mamoe.mirai.console.util + +/** + * 构造 [SemVersion.RangeRequirement] 的 DSL + */ +public object SemVersionRangeRequirementBuilder { + /** @see [SemVersion.parseRangeRequirement] */ + @ILoveHim188moeForever + public fun parse(rule: String): SemVersion.RangeRequirement = SemVersion.parseRangeRequirement(rule) + + @ILoveHim188moeForever + public infix fun SemVersion.RangeRequirement.or(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement { + return object : SemVersion.RangeRequirement { + override fun test(version: SemVersion): Boolean { + return this@or.test(version) || other.test(version) + } + + override fun toString(): String { + return "(${this@or}) or ($other)" + } + } + } + + @ILoveHim188moeForever + public infix fun String.or(other: String): SemVersion.RangeRequirement = parse(this) or parse(other) + + @ILoveHim188moeForever + public infix fun SemVersion.RangeRequirement.or(other: String): SemVersion.RangeRequirement = or(parse(other)) + + @ILoveHim188moeForever + public infix fun String.or(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement = parse(this) or other + + @ILoveHim188moeForever + public infix fun SemVersion.RangeRequirement.and(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement { + return object : SemVersion.RangeRequirement { + override fun test(version: SemVersion): Boolean { + return this@and.test(version) && other.test(version) + } + + override fun toString(): String { + return "(${this@and}) or ($other)" + } + } + } + + @ILoveHim188moeForever + public infix fun String.and(other: String): SemVersion.RangeRequirement = parse(this) and parse(other) + + @ILoveHim188moeForever + public infix fun SemVersion.RangeRequirement.and(other: String): SemVersion.RangeRequirement = and(parse(other)) + + @ILoveHim188moeForever + public infix fun String.and(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement = parse(this) and other + + @Suppress("NOTHING_TO_INLINE") + @ILoveHim188moeForever + public inline fun custom(rule: SemVersion.RangeRequirement): SemVersion.RangeRequirement = rule + + /** + * 标注一个 [SemVersionRangeRequirementBuilder] DSL + */ + @Suppress("SpellCheckingInspection") + @Retention(AnnotationRetention.BINARY) + @DslMarker + internal annotation class ILoveHim188moeForever + + /** [SemVersionRangeRequirementBuilder] 的使用示例 */ + @Suppress("unused") + private class ExampleOfBuilder { + val e1 = SemVersionRangeRequirementBuilder.run { + "1.0.0" or "1.1.5" + } + val e2 = SemVersionRangeRequirementBuilder.run { + parse("> 1.0.0") and parse("< 1.2.3") + } + val e3 = SemVersionRangeRequirementBuilder.run { + ("> 1.0.0" and "< 1.2.3") or "2.0.0" + } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index 8afa90d9a..87eda59e8 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.console -import com.vdurmont.semver4j.Semver import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager @@ -20,6 +19,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi +import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.LoginSolver @@ -43,8 +43,8 @@ fun initTestEnvironment() { get() = "Test" override val vendor: String get() = "Test" - override val version: Semver - get() = Semver("1.0.0") + override val version: SemVersion + get() = SemVersion.parse("1.0.0") } override val builtInPluginLoaders: List>> = listOf(lazy { JvmPluginLoader }) diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt index b1af02164..c6d790a76 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt @@ -23,7 +23,6 @@ package net.mamoe.mirai.console.terminal -import com.vdurmont.semver4j.Semver import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineName @@ -39,10 +38,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.terminal.ConsoleInputImpl.requestInput import net.mamoe.mirai.console.terminal.noconsole.AllEmptyLineReader import net.mamoe.mirai.console.terminal.noconsole.NoConsole -import net.mamoe.mirai.console.util.ConsoleExperimentalApi -import net.mamoe.mirai.console.util.ConsoleInput -import net.mamoe.mirai.console.util.ConsoleInternalApi -import net.mamoe.mirai.console.util.NamedSupervisorJob +import net.mamoe.mirai.console.util.* import net.mamoe.mirai.utils.* import org.fusesource.jansi.Ansi import org.jline.reader.LineReader @@ -155,7 +151,7 @@ val terminal: Terminal = run { private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { override val name: String get() = "Terminal" override val vendor: String get() = "Mamoe Technologies" - override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version + override val version: SemVersion = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version } private val ANSI_RESET = Ansi().reset().toString() From e26e98d0303ce14a525f4c9ee15d13d3d70fd8ed Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 18:54:51 +0800 Subject: [PATCH 079/114] Rename SemVersion.Companion.parse to SemVersion.Companion.invoke --- .../internal/MiraiConsoleBuildConstants.kt | 2 +- .../console/internal/util/SemVersionInternal.kt | 8 ++++---- .../console/plugin/jvm/JvmPluginDescription.kt | 6 +++--- .../net/mamoe/mirai/console/util/SemVersion.kt | 17 ++++++++++++----- .../net/mamoe/mirai/console/TestMiraiConosle.kt | 2 +- .../mamoe/mirai/console/util/TestSemVersion.kt | 6 +++--- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index f1889aeab..f314be39c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -17,5 +17,5 @@ internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mira val buildDate: Instant = Instant.ofEpochSecond(1600596035) @JvmStatic - val version: SemVersion = SemVersion.parse("1.0-RC-dev-28") + val version: SemVersion = SemVersion("1.0-RC-dev-28") } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index bcbd996a6..85a05ac4e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -85,7 +85,7 @@ internal object SemVersionInternal { private fun String.parseRule(): SemVersion.RangeRequirement { val trimmed = trim() if (directVersion.matches(trimmed)) { - val parsed = SemVersion.parse(trimmed) + val parsed = SemVersion.invoke(trimmed) return SemVersion.RangeRequirement { it.compareTo(parsed) == 0 } @@ -101,8 +101,8 @@ internal object SemVersionInternal { } } (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range -> - var start = SemVersion.parse(range.groupValues[1]) - var end = SemVersion.parse(range.groupValues[4]) + var start = SemVersion.invoke(range.groupValues[1]) + var end = SemVersion.invoke(range.groupValues[4]) if (start > end) { val c = end end = start @@ -115,7 +115,7 @@ internal object SemVersionInternal { } versionRule.matchEntire(trimmed)?.let { result -> val operator = result.groupValues[1] - val version = SemVersion.parse(result.groupValues[7]) + val version = SemVersion.invoke(result.groupValues[7]) return when (operator) { ">=" -> { SemVersion.RangeRequirement { it >= version } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 242249f21..1948c7b9a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -105,7 +105,7 @@ public class JvmPluginDescriptionBuilder( public constructor( @ResolveContext(PLUGIN_NAME) id: String, @ResolveContext(PLUGIN_VERSION) version: String, - ) : this(id, SemVersion.parse(version)) + ) : this(id, SemVersion(version)) private var name: String = id private var author: String = "" @@ -118,7 +118,7 @@ public class JvmPluginDescriptionBuilder( @ILoveKuriyamaMiraiForever public fun version(@ResolveContext(PLUGIN_VERSION) value: String): JvmPluginDescriptionBuilder = - apply { this.version = SemVersion.parse(value) } + apply { this.version = SemVersion(value) } @ILoveKuriyamaMiraiForever public fun version(@ResolveContext(PLUGIN_VERSION) value: SemVersion): JvmPluginDescriptionBuilder = @@ -281,7 +281,7 @@ public data class SimpleJvmPluginDescription author: String = "", info: String = "", dependencies: Set = setOf(), - ) : this(name, SemVersion.parse(version), id, author, info, dependencies) + ) : this(name, SemVersion(version), id, author, info, dependencies) init { require(!name.contains(':')) { "':' is forbidden in plugin name" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 38903ccbd..a1abf883c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -21,6 +21,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals @@ -44,15 +45,20 @@ import net.mamoe.mirai.console.util.SemVersion.RangeRequirement * 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在. * * @see RangeRequirement + * @see SemVersion.invoke */ @Serializable(with = SemVersion.SemVersionAsStringSerializer::class) -public data class SemVersion internal constructor( +public data class SemVersion +/** + * @see SemVersion.invoke 字符串解析 + */ +internal constructor( /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */ public val mainVersion: IntArray, /** 先行版本号识别符 */ public val identifier: String? = null, /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ - public val metadata: String? = null + public val metadata: String? = null, ) : Comparable { /** * 一条依赖规则 @@ -65,7 +71,7 @@ public data class SemVersion internal constructor( public object SemVersionAsStringSerializer : KSerializer by String.serializer().map( serializer = { it.toString() }, - deserializer = { parse(it) } + deserializer = { SemVersion(it) } ) public companion object { @@ -90,7 +96,8 @@ public data class SemVersion internal constructor( */ @Throws(IllegalArgumentException::class, NumberFormatException::class) @JvmStatic - public fun parse(@ResolveContext(ResolveContext.Kind.PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) + @JvmName("parse") + public operator fun invoke(@ResolveContext(PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) /** * 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException] @@ -121,7 +128,7 @@ public data class SemVersion internal constructor( /** @see [RangeRequirement.test] */ @JvmStatic - public fun RangeRequirement.test(version: String): Boolean = test(parse(version)) + public fun RangeRequirement.test(version: String): Boolean = test(invoke(version)) /** * 当满足 [requirement] 时返回 true, 否则返回 false diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index 87eda59e8..969f03425 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -44,7 +44,7 @@ fun initTestEnvironment() { override val vendor: String get() = "Test" override val version: SemVersion - get() = SemVersion.parse("1.0.0") + get() = SemVersion("1.0.0") } override val builtInPluginLoaders: List>> = listOf(lazy { JvmPluginLoader }) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index b76d610e1..e2ae8a8c1 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test internal class TestSemVersion { @Test internal fun testCompare() { - fun String.sem(): SemVersion = SemVersion.parse(this) + fun String.sem(): SemVersion = SemVersion.invoke(this) assert("1.0".sem() < "1.0.1".sem()) assert("1.0.0".sem() == "1.0".sem()) assert("1.1".sem() > "1.0.0".sem()) @@ -85,12 +85,12 @@ internal class TestSemVersion { } private fun String.check() { - val sem = SemVersion.parse(this) + val sem = SemVersion.invoke(this) assert(this == sem.toString()) { "$this != $sem" } } private fun String.checkInvalid() { - kotlin.runCatching { SemVersion.parse(this) } + kotlin.runCatching { SemVersion.invoke(this) } .onSuccess { assert(false) { "$this not a invalid sem-version" } } } From 09de9e7cd7f3139e48ea5193a7aa5edc346f5303 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 18:57:10 +0800 Subject: [PATCH 080/114] Make SimpleJvmPluginDescription internal --- .../plugin/jvm/JvmPluginDescription.kt | 55 ++++--------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 1948c7b9a..96a013405 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -34,6 +34,7 @@ public interface JvmPluginDescription : PluginDescription { * 构建 [JvmPluginDescription] * @see JvmPluginDescriptionBuilder */ + @JvmName("create") @JvmSynthetic public operator fun invoke( /** @@ -55,8 +56,7 @@ public interface JvmPluginDescription : PluginDescription { * 构建 [JvmPluginDescription] * @see JvmPluginDescriptionBuilder */ - @Suppress("DEPRECATION_ERROR") - @Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR) + @JvmName("create") @JvmSynthetic public operator fun invoke( /** @@ -101,9 +101,8 @@ public class JvmPluginDescriptionBuilder( private var id: String, private var version: SemVersion, ) { - @Suppress("DEPRECATION_ERROR") public constructor( - @ResolveContext(PLUGIN_NAME) id: String, + @ResolveContext(PLUGIN_ID) id: String, @ResolveContext(PLUGIN_VERSION) version: String, ) : this(id, SemVersion(version)) @@ -232,49 +231,19 @@ public class JvmPluginDescriptionBuilder( * * @see JvmPluginDescription */ -@Deprecated( - """ - 将在 1.0-RC 删除. 请使用 JvmPluginDescription. -""", - replaceWith = ReplaceWith( - "JvmPluginDescription", - "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription" - ), - level = DeprecationLevel.ERROR -) -public data class SimpleJvmPluginDescription -@Deprecated( - """ - 构造器不稳定, 将在 1.0-RC 删除. 请使用 JvmPluginDescriptionBuilder. -""", - replaceWith = ReplaceWith( - "JvmPluginDescription(name, version) {}", - "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription.Companion.invoke" - ), - level = DeprecationLevel.ERROR -) -@JvmOverloads public constructor( - public override val name: String, - public override val version: SemVersion, - public override val id: String = name, - public override val author: String = "", - public override val info: String = "", - public override val dependencies: Set = setOf(), +internal data class SimpleJvmPluginDescription +@JvmOverloads constructor( + override val name: String, + override val version: SemVersion, + override val id: String = name, + override val author: String = "", + override val info: String = "", + override val dependencies: Set = setOf(), ) : JvmPluginDescription { - @Deprecated( - """ - 构造器不稳定, 将在 1.0-RC 删除. 请使用 JvmPluginDescriptionBuilder. -""", - replaceWith = ReplaceWith( - "JvmPluginDescription.invoke(name, version) {}", - "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription.Companion.invoke" - ), - level = DeprecationLevel.ERROR - ) @Suppress("DEPRECATION_ERROR") @JvmOverloads - public constructor( + constructor( name: String, version: String, id: String = name, From 47750c506069cd60e9c00b6f86e870a30cd50729 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 18:58:26 +0800 Subject: [PATCH 081/114] Add ResolveContext for PluginDependency --- .../console/plugin/description/PluginDependency.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt index d22883093..25bedabc8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt @@ -11,6 +11,8 @@ package net.mamoe.mirai.console.plugin.description +import net.mamoe.mirai.console.compiler.common.ResolveContext +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_ID import net.mamoe.mirai.console.util.SemVersion /** @@ -22,7 +24,7 @@ public data class PluginDependency @JvmOverloads constructor( /** * 依赖插件 ID, [PluginDescription.id] */ - public val id: String, + @ResolveContext(PLUGIN_ID) public val id: String, /** * 依赖版本号. 为 null 时则为不限制版本. * @@ -48,7 +50,10 @@ public data class PluginDependency @JvmOverloads constructor( /** * @see PluginDependency */ - public constructor(name: String, isOptional: Boolean = false) : this( - name, null, isOptional + public constructor( + @ResolveContext(PLUGIN_ID) id: String, + isOptional: Boolean = false, + ) : this( + id, null, isOptional ) } \ No newline at end of file From 2035e136f3b45f0ed83b766ceafccd68aa9e51a7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 18:59:37 +0800 Subject: [PATCH 082/114] Rename SemVersion.RangeRequirement to SemVersion.Requirement --- .../internal/util/SemVersionInternal.kt | 32 +++++++++---------- .../plugin/description/PluginDependency.kt | 5 ++- .../plugin/jvm/JvmPluginDescription.kt | 6 ++-- .../mamoe/mirai/console/util/SemVersion.kt | 18 +++++------ .../util/SemVersionRangeRequirementBuilder.kt | 26 +++++++-------- .../mirai/console/util/TestSemVersion.kt | 4 +-- 6 files changed, 45 insertions(+), 46 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index 85a05ac4e..6f31a6a1f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -82,21 +82,21 @@ internal object SemVersionInternal { } @JvmStatic - private fun String.parseRule(): SemVersion.RangeRequirement { + private fun String.parseRule(): SemVersion.Requirement { val trimmed = trim() if (directVersion.matches(trimmed)) { val parsed = SemVersion.invoke(trimmed) - return SemVersion.RangeRequirement { + return SemVersion.Requirement { it.compareTo(parsed) == 0 } } if (versionSelect.matches(trimmed)) { val regex = ("^" + - trimmed.replace(".", "\\.") + trimmed.replace(".", "\\.") .replace("x", ".+") + "$" ).toRegex() - return SemVersion.RangeRequirement { + return SemVersion.Requirement { regex.matches(it.toString()) } } @@ -109,7 +109,7 @@ internal object SemVersionInternal { start = c } val compareRange = start..end - return SemVersion.RangeRequirement { + return SemVersion.Requirement { it in compareRange } } @@ -118,19 +118,19 @@ internal object SemVersionInternal { val version = SemVersion.invoke(result.groupValues[7]) return when (operator) { ">=" -> { - SemVersion.RangeRequirement { it >= version } + SemVersion.Requirement { it >= version } } ">" -> { - SemVersion.RangeRequirement { it > version } + SemVersion.Requirement { it > version } } "<=" -> { - SemVersion.RangeRequirement { it <= version } + SemVersion.Requirement { it <= version } } "<" -> { - SemVersion.RangeRequirement { it < version } + SemVersion.Requirement { it < version } } "=" -> { - SemVersion.RangeRequirement { it.compareTo(version) == 0 } + SemVersion.Requirement { it.compareTo(version) == 0 } } else -> throw AssertionError("operator=$operator, version=$version") } @@ -138,8 +138,8 @@ internal object SemVersionInternal { throw UnsupportedOperationException("Cannot parse $this") } - private fun SemVersion.RangeRequirement.withRule(rule: String): SemVersion.RangeRequirement { - return object : SemVersion.RangeRequirement { + private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement { + return object : SemVersion.Requirement { override fun test(version: SemVersion): Boolean { return this@withRule.test(version) } @@ -151,7 +151,7 @@ internal object SemVersionInternal { } @JvmStatic - fun parseRangeRequirement(requirement: String): SemVersion.RangeRequirement { + fun parseRangeRequirement(requirement: String): SemVersion.Requirement { if (requirement.isBlank()) { throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") } @@ -159,11 +159,11 @@ internal object SemVersionInternal { it.parseRule().withRule(it) }.let { checks -> if (checks.size == 1) return checks[0] - SemVersion.RangeRequirement { + SemVersion.Requirement { checks.forEach { rule -> - if (rule.test(it)) return@RangeRequirement true + if (rule.test(it)) return@Requirement true } - return@RangeRequirement false + return@Requirement false }.withRule(requirement) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt index 25bedabc8..d73ac9cec 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDependency.kt @@ -30,10 +30,9 @@ public data class PluginDependency @JvmOverloads constructor( * * 版本遵循 [语义化版本 2.0 规范](https://semver.org/lang/zh-CN/), * - * ### 示例 - * `Requirement.buildIvy("[1.0, 2.0)")` + * @see SemVersion.Requirement */ - public val versionRequirement: SemVersion.RangeRequirement? = null, + public val versionRequirement: SemVersion.Requirement? = null, /** * 若为 `false`, 插件在找不到此依赖时也能正常加载. */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 96a013405..736517b52 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -156,7 +156,7 @@ public class JvmPluginDescriptionBuilder( public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, - versionRequirement: SemVersion.RangeRequirement, + versionRequirement: SemVersion.Requirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional)) } @@ -169,7 +169,7 @@ public class JvmPluginDescriptionBuilder( @ILoveKuriyamaMiraiForever public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, - versionRequirement: SemVersion.RangeRequirement, + versionRequirement: SemVersion.Requirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, versionRequirement, false)) } @@ -204,7 +204,7 @@ public class JvmPluginDescriptionBuilder( public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, - versionRequirement: SemVersionRangeRequirementBuilder.() -> SemVersion.RangeRequirement, + versionRequirement: SemVersionRangeRequirementBuilder.() -> SemVersion.Requirement, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index a1abf883c..b209a7226 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -25,7 +25,7 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSIO import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals -import net.mamoe.mirai.console.util.SemVersion.RangeRequirement +import net.mamoe.mirai.console.util.SemVersion.Requirement /** * [语义化版本](https://semver.org/lang/zh-CN/) 支持 @@ -44,7 +44,7 @@ import net.mamoe.mirai.console.util.SemVersion.RangeRequirement * * 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在. * - * @see RangeRequirement + * @see Requirement * @see SemVersion.invoke */ @Serializable(with = SemVersion.SemVersionAsStringSerializer::class) @@ -64,7 +64,7 @@ internal constructor( * 一条依赖规则 * @see [parseRangeRequirement] */ - public fun interface RangeRequirement { + public fun interface Requirement { /** 在 [version] 满足此要求时返回 true */ public fun test(version: SemVersion): Boolean } @@ -124,27 +124,27 @@ internal constructor( */ @Throws(IllegalArgumentException::class) @JvmStatic - public fun parseRangeRequirement(requirement: String): RangeRequirement = SemVersionInternal.parseRangeRequirement(requirement) + public fun parseRangeRequirement(requirement: String): Requirement = SemVersionInternal.parseRangeRequirement(requirement) - /** @see [RangeRequirement.test] */ + /** @see [Requirement.test] */ @JvmStatic - public fun RangeRequirement.test(version: String): Boolean = test(invoke(version)) + public fun Requirement.test(version: String): Boolean = test(invoke(version)) /** * 当满足 [requirement] 时返回 true, 否则返回 false */ @JvmStatic - public fun SemVersion.satisfies(requirement: RangeRequirement): Boolean = requirement.test(this) + public fun SemVersion.satisfies(requirement: Requirement): Boolean = requirement.test(this) /** for Kotlin only */ @JvmStatic @JvmSynthetic - public operator fun RangeRequirement.contains(version: SemVersion): Boolean = test(version) + public operator fun Requirement.contains(version: SemVersion): Boolean = test(version) /** for Kotlin only */ @JvmStatic @JvmSynthetic - public operator fun RangeRequirement.contains(version: String): Boolean = test(version) + public operator fun Requirement.contains(version: String): Boolean = test(version) } @Transient diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt index ec4d663a4..74145d1f8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt @@ -11,16 +11,16 @@ package net.mamoe.mirai.console.util /** - * 构造 [SemVersion.RangeRequirement] 的 DSL + * 构造 [SemVersion.Requirement] 的 DSL */ public object SemVersionRangeRequirementBuilder { /** @see [SemVersion.parseRangeRequirement] */ @ILoveHim188moeForever - public fun parse(rule: String): SemVersion.RangeRequirement = SemVersion.parseRangeRequirement(rule) + public fun parse(rule: String): SemVersion.Requirement = SemVersion.parseRangeRequirement(rule) @ILoveHim188moeForever - public infix fun SemVersion.RangeRequirement.or(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement { - return object : SemVersion.RangeRequirement { + public infix fun SemVersion.Requirement.or(other: SemVersion.Requirement): SemVersion.Requirement { + return object : SemVersion.Requirement { override fun test(version: SemVersion): Boolean { return this@or.test(version) || other.test(version) } @@ -32,17 +32,17 @@ public object SemVersionRangeRequirementBuilder { } @ILoveHim188moeForever - public infix fun String.or(other: String): SemVersion.RangeRequirement = parse(this) or parse(other) + public infix fun String.or(other: String): SemVersion.Requirement = parse(this) or parse(other) @ILoveHim188moeForever - public infix fun SemVersion.RangeRequirement.or(other: String): SemVersion.RangeRequirement = or(parse(other)) + public infix fun SemVersion.Requirement.or(other: String): SemVersion.Requirement = or(parse(other)) @ILoveHim188moeForever - public infix fun String.or(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement = parse(this) or other + public infix fun String.or(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) or other @ILoveHim188moeForever - public infix fun SemVersion.RangeRequirement.and(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement { - return object : SemVersion.RangeRequirement { + public infix fun SemVersion.Requirement.and(other: SemVersion.Requirement): SemVersion.Requirement { + return object : SemVersion.Requirement { override fun test(version: SemVersion): Boolean { return this@and.test(version) && other.test(version) } @@ -54,17 +54,17 @@ public object SemVersionRangeRequirementBuilder { } @ILoveHim188moeForever - public infix fun String.and(other: String): SemVersion.RangeRequirement = parse(this) and parse(other) + public infix fun String.and(other: String): SemVersion.Requirement = parse(this) and parse(other) @ILoveHim188moeForever - public infix fun SemVersion.RangeRequirement.and(other: String): SemVersion.RangeRequirement = and(parse(other)) + public infix fun SemVersion.Requirement.and(other: String): SemVersion.Requirement = and(parse(other)) @ILoveHim188moeForever - public infix fun String.and(other: SemVersion.RangeRequirement): SemVersion.RangeRequirement = parse(this) and other + public infix fun String.and(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) and other @Suppress("NOTHING_TO_INLINE") @ILoveHim188moeForever - public inline fun custom(rule: SemVersion.RangeRequirement): SemVersion.RangeRequirement = rule + public inline fun custom(rule: SemVersion.Requirement): SemVersion.Requirement = rule /** * 标注一个 [SemVersionRangeRequirementBuilder] DSL diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index e2ae8a8c1..c7325c177 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -43,12 +43,12 @@ internal class TestSemVersion { @Test internal fun testRequirement() { - fun SemVersion.RangeRequirement.assert(version: String): SemVersion.RangeRequirement { + fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement { assert(test(version)) { version } return this } - fun SemVersion.RangeRequirement.assertFalse(version: String): SemVersion.RangeRequirement { + fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement { assert(!test(version)) { version } return this } From 9514a5c3e8c22800c3c62f54a6aa4a26024f0e6e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 18:59:59 +0800 Subject: [PATCH 083/114] Make SemVersion.Requirement not fun --- .../src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index b209a7226..e4940f1be 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -64,7 +64,7 @@ internal constructor( * 一条依赖规则 * @see [parseRangeRequirement] */ - public fun interface Requirement { + public interface Requirement { /** 在 [version] 满足此要求时返回 true */ public fun test(version: SemVersion): Boolean } From 57c30e4689dd25d312a7eb49462e49172388a8d2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 19:03:50 +0800 Subject: [PATCH 084/114] Fix build, Fix code style --- .../internal/util/SemVersionInternal.kt | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index 6f31a6a1f..02cec4490 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -26,11 +26,6 @@ internal object SemVersionInternal { private val versionMathRange = """\[([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))\]""".toRegex() private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex() - private fun Collection<*>.dump() { - forEachIndexed { index, value -> - println("$index, $value") - } - } 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() @@ -86,18 +81,18 @@ internal object SemVersionInternal { val trimmed = trim() if (directVersion.matches(trimmed)) { val parsed = SemVersion.invoke(trimmed) - return SemVersion.Requirement { - it.compareTo(parsed) == 0 + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0 } } if (versionSelect.matches(trimmed)) { val regex = ("^" + trimmed.replace(".", "\\.") - .replace("x", ".+") + - "$" - ).toRegex() - return SemVersion.Requirement { - regex.matches(it.toString()) + .replace("x", ".+") + + "$" + ).toRegex() + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = regex.matches(version.toString()) } } (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range -> @@ -109,44 +104,49 @@ internal object SemVersionInternal { start = c } val compareRange = start..end - return SemVersion.Requirement { - it in compareRange + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version in compareRange } } versionRule.matchEntire(trimmed)?.let { result -> val operator = result.groupValues[1] - val version = SemVersion.invoke(result.groupValues[7]) + val version1 = SemVersion.invoke(result.groupValues[7]) return when (operator) { ">=" -> { - SemVersion.Requirement { it >= version } + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version >= version1 + } } ">" -> { - SemVersion.Requirement { it > version } + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version > version1 + } } "<=" -> { - SemVersion.Requirement { it <= version } + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version <= version1 + } } "<" -> { - SemVersion.Requirement { it < version } + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version < version1 + } } "=" -> { - SemVersion.Requirement { it.compareTo(version) == 0 } + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0 + } } - else -> throw AssertionError("operator=$operator, version=$version") + else -> error("operator=$operator, version=$version1") } } - throw UnsupportedOperationException("Cannot parse $this") + throw IllegalArgumentException("Cannot parse $this") } private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement { return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@withRule.test(version) - } - - override fun toString(): String { - return rule - } + override fun test(version: SemVersion): Boolean = this@withRule.test(version) + override fun toString(): String = rule } } @@ -159,11 +159,13 @@ internal object SemVersionInternal { it.parseRule().withRule(it) }.let { checks -> if (checks.size == 1) return checks[0] - SemVersion.Requirement { - checks.forEach { rule -> - if (rule.test(it)) return@Requirement true + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean { + checks.forEach { rule -> + if (rule.test(version)) return true + } + return false } - return@Requirement false }.withRule(requirement) } } From 704674698f41b2aaf87fbcf007b9ac6b1f0fc658 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 19:05:45 +0800 Subject: [PATCH 085/114] Add ResolveContext --- .../main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index e4940f1be..095280b90 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -124,11 +124,12 @@ internal constructor( */ @Throws(IllegalArgumentException::class) @JvmStatic - public fun parseRangeRequirement(requirement: String): Requirement = SemVersionInternal.parseRangeRequirement(requirement) + public fun parseRangeRequirement(requirement: String): Requirement = + SemVersionInternal.parseRangeRequirement(requirement) /** @see [Requirement.test] */ @JvmStatic - public fun Requirement.test(version: String): Boolean = test(invoke(version)) + public fun Requirement.test(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(invoke(version)) /** * 当满足 [requirement] 时返回 true, 否则返回 false @@ -144,7 +145,7 @@ internal constructor( /** for Kotlin only */ @JvmStatic @JvmSynthetic - public operator fun Requirement.contains(version: String): Boolean = test(version) + public operator fun Requirement.contains(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(version) } @Transient From 453ad8f9e26c301c5241c59cb8e2efe8518a37d5 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 19:32:00 +0800 Subject: [PATCH 086/114] Add ImageArgumentParser, fix #183 --- .../description/CommandArgumentContext.kt | 2 + .../CommandArgumentParserBuiltins.kt | 37 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt index 8cf2456b5..47d02e1c3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.Image import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -74,6 +75,7 @@ public interface CommandArgumentContext { Long::class with LongArgumentParser Double::class with DoubleArgumentParser Float::class with FloatArgumentParser + Image::class with ImageArgumentParser Contact::class with ExistingContactArgumentParser User::class with ExistingUserArgumentParser diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt index 9e5e22fe4..851ba009c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt @@ -19,10 +19,7 @@ import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.contact.* import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getGroupOrNull -import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.message.data.MessageContent -import net.mamoe.mirai.message.data.SingleMessage -import net.mamoe.mirai.message.data.content +import net.mamoe.mirai.message.data.* /** @@ -80,15 +77,33 @@ public object StringArgumentParser : InternalCommandArgumentParserExtensions { + public override fun parse(raw: String, sender: CommandSender): Image { + return kotlin.runCatching { + Image(raw) + }.getOrElse { + illegalArgument("无法解析 $raw 为图片.") + } + } + + override fun parse(raw: MessageContent, sender: CommandSender): Image { + if (raw is Image) return raw + return super.parse(raw, sender) + } +} + /** * 当字符串内容为(不区分大小写) "true", "yes", "enabled" */ public object BooleanArgumentParser : InternalCommandArgumentParserExtensions { public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str -> str.equals("true", ignoreCase = true) - || str.equals("yes", ignoreCase = true) - || str.equals("enabled", ignoreCase = true) - || str.equals("on", ignoreCase = true) + || str.equals("yes", ignoreCase = true) + || str.equals("enabled", ignoreCase = true) + || str.equals("on", ignoreCase = true) } } @@ -365,10 +380,10 @@ internal interface InternalCommandArgumentParserExtensions : CommandArg } else { var index = 1 illegalArgument("无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" + - candidates.joinToString("\n", limit = 6) { - val percentage = (it.second * 100).toDecimalPlace(0) - "#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4% - } + candidates.joinToString("\n", limit = 6) { + val percentage = (it.second * 100).toDecimalPlace(0) + "#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4% + } ) } } From e10a17ccd4fb5f81f732ce9814748c94b8f8061e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 19:33:21 +0800 Subject: [PATCH 087/114] Add PlainTextArgumentParser --- .../command/description/CommandArgumentContext.kt | 3 +++ .../description/CommandArgumentParserBuiltins.kt | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt index 47d02e1c3..cacea64e0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt @@ -21,6 +21,7 @@ import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.PlainText import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -75,7 +76,9 @@ public interface CommandArgumentContext { Long::class with LongArgumentParser Double::class with DoubleArgumentParser Float::class with FloatArgumentParser + Image::class with ImageArgumentParser + PlainText::class with PlainTextArgumentParser Contact::class with ExistingContactArgumentParser User::class with ExistingUserArgumentParser diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt index 851ba009c..28d49cd11 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt @@ -95,6 +95,17 @@ public object ImageArgumentParser : InternalCommandArgumentParserExtensions { + public override fun parse(raw: String, sender: CommandSender): PlainText { + return PlainText(raw) + } + + override fun parse(raw: MessageContent, sender: CommandSender): PlainText { + if (raw is PlainText) return raw + return super.parse(raw, sender) + } +} + /** * 当字符串内容为(不区分大小写) "true", "yes", "enabled" */ From 71a02e7630adcc981b87d5c9fbf449d09bd40229 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 21:32:05 +0800 Subject: [PATCH 088/114] Import yamlkt using 'implementation', improve buildscript --- backend/mirai-console/build.gradle.kts | 5 ++--- buildSrc/src/main/kotlin/dependencyExtensions.kt | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 6aad5bba4..3bb13026b 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -65,10 +65,9 @@ dependencies { compileAndTestRuntime(kotlinx("serialization-core", Versions.serialization)) compileAndTestRuntime(kotlin("reflect")) - implementation("org.jetbrains:annotations:19.0.0") - + smartImplementation("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") + smartImplementation("org.jetbrains:annotations:19.0.0") smartApi(kotlinx("coroutines-jdk8", Versions.coroutines)) - smartApi("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(kotlin("stdlib-jdk8")) diff --git a/buildSrc/src/main/kotlin/dependencyExtensions.kt b/buildSrc/src/main/kotlin/dependencyExtensions.kt index d1b817404..76b6fd9b5 100644 --- a/buildSrc/src/main/kotlin/dependencyExtensions.kt +++ b/buildSrc/src/main/kotlin/dependencyExtensions.kt @@ -26,9 +26,22 @@ fun DependencyHandler.compileAndTestRuntime(any: Any) { fun DependencyHandler.smartApi( dependencyNotation: String +): ExternalModuleDependency { + return smart("api", dependencyNotation) +} + +fun DependencyHandler.smartImplementation( + dependencyNotation: String +): ExternalModuleDependency { + return smart("implementation", dependencyNotation) +} + +private fun DependencyHandler.smart( + configuration: String, + dependencyNotation: String ): ExternalModuleDependency { return addDependencyTo( - this, "api", dependencyNotation + this, configuration, dependencyNotation ) { fun exclude(group: String, module: String) { exclude(mapOf( From 91c5f5f134245d25b1ee87f319ef782e74c08c35 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 21:32:38 +0800 Subject: [PATCH 089/114] Use JSON to store data if YAML failed --- .../data/MultiFilePluginDataStorageImpl.kt | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt index 351ea18ea..45fa24f88 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/MultiFilePluginDataStorageImpl.kt @@ -9,6 +9,8 @@ package net.mamoe.mirai.console.internal.data +import kotlinx.serialization.json.Json +import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.util.ConsoleExperimentalApi @@ -59,23 +61,28 @@ internal open class MultiFilePluginDataStorageImpl( return file.toFile().also { it.createNewFile() } } + private val json = Json { + prettyPrint = true + ignoreUnknownKeys = true + isLenient = true + allowStructuredMapKeys = true + } + + private val yaml = Yaml.default + @ConsoleExperimentalApi public override fun store(holder: PluginDataHolder, instance: PluginData) { - val yaml =/* if (instance.saveName == "PermissionService") Json { - prettyPrint = true - ignoreUnknownKeys = true - isLenient = true - allowStructuredMapKeys = true - } /*Yaml( - configuration = YamlConfiguration( - mapSerialization = YamlConfiguration.MapSerialization.FLOW_MAP, - listSerialization = YamlConfiguration.ListSerialization.FLOW_SEQUENCE, - classSerialization = YamlConfiguration.MapSerialization.FLOW_MAP - ) - )*/ else */Yaml.default getPluginDataFile(holder, instance).writeText( kotlin.runCatching { yaml.encodeToString(instance.updaterSerializer, Unit) + }.recoverCatching { + // Just use mainLogger for convenience. + MiraiConsole.mainLogger.warning( + "Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " + + "Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new", + it + ) + json.encodeToString(instance.updaterSerializer, Unit) }.getOrElse { throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it) } From 13a0444244bd5077f17d49f78ab7ad97b55f1faa Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 21:35:31 +0800 Subject: [PATCH 090/114] Fix deprecation message on PluginData implementations --- .../net/mamoe/mirai/console/data/AutoSavePluginConfig.kt | 2 +- .../net/mamoe/mirai/console/data/AutoSavePluginData.kt | 6 +++--- .../mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt | 2 +- .../mamoe/mirai/console/data/java/JAutoSavePluginData.kt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt index df4a4c280..1e3793e5a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginConfig.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.Job * @see AutoSavePluginData */ public open class AutoSavePluginConfig : AutoSavePluginData, PluginConfig { - @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"改成保存的名称\")")) + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"把我改成保存名称\")")) @Suppress("DEPRECATION_ERROR") public constructor() : super() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt index 06410a113..a8405df03 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -30,7 +30,7 @@ import kotlin.reflect.full.findAnnotation * @see PluginData */ public open class AutoSavePluginData private constructor( - @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any? + @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, ) : AbstractPluginData() { private lateinit var owner_: AutoSavePluginDataHolder private val autoSaveIntervalMillis_: LongRange get() = owner_.autoSaveIntervalMillis @@ -45,7 +45,7 @@ public open class AutoSavePluginData private constructor( _saveName = saveName } - @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig")) + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginData(\"把我改成保存名称\")")) public constructor() : this(null) { val clazz = this::class _saveName = clazz.findAnnotation()?.value @@ -72,7 +72,7 @@ public open class AutoSavePluginData private constructor( ?.let { return@invokeOnCompletion } MiraiConsole.mainLogger.error( "An exception occurred when saving config ${this@AutoSavePluginData::class.qualifiedNameOrTip} " + - "but CoroutineExceptionHandler not found in PluginDataHolder.coroutineContext for ${owner::class.qualifiedNameOrTip}", + "but CoroutineExceptionHandler not found in PluginDataHolder.coroutineContext for ${owner::class.qualifiedNameOrTip}", e ) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt index 5d9788dd1..1ca820466 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginConfig.kt @@ -38,7 +38,7 @@ import net.mamoe.mirai.console.data.PluginData * @see PluginConfig */ public abstract class JAutoSavePluginConfig : AutoSavePluginConfig, PluginConfig { - @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"改成保存的名称\")")) + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("JAutoSavePluginConfig(\"把我改成保存名称\")")) @Suppress("DEPRECATION_ERROR") public constructor() : super() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt index ec2986fbc..442f19cc9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/java/JAutoSavePluginData.kt @@ -67,7 +67,7 @@ import kotlin.reflect.full.createType * @see PluginData */ public abstract class JAutoSavePluginData : AutoSavePluginData, PluginConfig { - @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"改成保存的名称\")")) + @Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("JAutoSavePluginData(\"把我改成保存名称\")")) @Suppress("DEPRECATION_ERROR") public constructor() : super() From ff68342110049995bdf9882b31eb22be60fd121b Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 21:38:31 +0800 Subject: [PATCH 091/114] Declare ValueName as stable --- .../main/kotlin/net/mamoe/mirai/console/data/ValueName.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt index d1017080b..e9de3a500 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt @@ -9,14 +9,12 @@ package net.mamoe.mirai.console.data -import net.mamoe.mirai.console.util.ConsoleExperimentalApi - /** * 序列化之后的名称. * * 例: * ``` - * object AccountPluginData : PluginData by ... { + * object AccountPluginData : AutoSavePluginData() { * @ValueName("info") * val map: Map by value("a" to "b") * } @@ -28,8 +26,10 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi * map: * a: b * ``` + * + * @see PluginData + * @see Value */ -@ConsoleExperimentalApi @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) public annotation class ValueName(val value: String) From 7a8944b1d6ff50689d90ae19fd8599bb8fbc9f41 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Sep 2020 21:38:43 +0800 Subject: [PATCH 092/114] Add note for reserved primary constructor --- .../kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt index a8405df03..0ab74fc19 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AutoSavePluginData.kt @@ -30,6 +30,7 @@ import kotlin.reflect.full.findAnnotation * @see PluginData */ public open class AutoSavePluginData private constructor( + // KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, ) : AbstractPluginData() { private lateinit var owner_: AutoSavePluginDataHolder From 62527f0ed0ce4e2d676341c3bfd884eb9c98c72e Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Sep 2020 22:48:54 +0800 Subject: [PATCH 093/114] Extract SemVersion.major, minor, patch --- .../internal/util/SemVersionInternal.kt | 19 ++++++------ .../mamoe/mirai/console/util/SemVersion.kt | 30 ++++++++++++------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt index 02cec4490..2cbe56ec4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt @@ -69,8 +69,11 @@ internal object SemVersionInternal { } } } + val mainVersion = version.substring(0, mainVersionEnd).parseMainVersion() return SemVersion( - mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(), + major = mainVersion[0], + minor = mainVersion[1], + patch = mainVersion.getOrNull(2), identifier = identifier, metadata = metadata ) @@ -176,16 +179,12 @@ internal object SemVersionInternal { // If $this equals $other (without metadata), // return same. - if (other.mainVersion.contentEquals(source.mainVersion) && source.identifier == other.identifier) { - return 0 - } - fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 } - // Compare main-version - for (index in 0 until (max(source.mainVersion.size, other.mainVersion.size))) { - val result = source.mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index)) - if (result != 0) return result - } + + source.major.compareTo(other.major).takeUnless { it == 0 }?.let { return it } + source.minor.compareTo(other.minor).takeUnless { it == 0 }?.let { return it } + (source.patch ?: 0).compareTo(other.patch ?: 0).takeUnless { it == 0 }?.let { return it } + // If main-versions are same. var identifier0 = source.identifier var identifier1 = other.identifier diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index 095280b90..bed586514 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -32,10 +32,13 @@ import net.mamoe.mirai.console.util.SemVersion.Requirement * * 解析示例: * - * `1.0.0-M4+c25733b8` 将会解析出三个内容, mainVersion (核心版本号), [identifier] (先行版本号) 和 [metadata] (元数据). + * `1.0.0-M4+c25733b8` 将会解析出下面的内容, + * [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据). * ``` * SemVersion( - * mainVersion = IntArray [1, 0, 0], + * major = 1, + * minor = 0, + * patch = 0, * identifier = "M4" * metadata = "c25733b8" * ) @@ -53,8 +56,12 @@ public data class SemVersion * @see SemVersion.invoke 字符串解析 */ internal constructor( - /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */ - public val mainVersion: IntArray, + /** 主版本号 */ + public val major: Int, + /** 次版本号 */ + public val minor: Int, + /** 修订号 */ + public val patch: Int?, /** 先行版本号识别符 */ public val identifier: String? = null, /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ @@ -151,14 +158,14 @@ internal constructor( @Transient private val toString: String by lazy(LazyThreadSafetyMode.NONE) { buildString { - mainVersion.joinTo(this, ".") + append(major) + append('.').append(minor) + patch?.let { append('.').append(it) } identifier?.let { identifier -> - append('-') - append(identifier) + append('-').append(identifier) } metadata?.let { metadata -> - append('+') - append(metadata) + append('+').append(metadata) } } } @@ -169,7 +176,7 @@ internal constructor( * 将 [SemVersion] 转为 Kotlin data class 风格的 [String] */ public fun toStructuredString(): String { - return "SemVersion(mainVersion=${mainVersion.contentToString()}, identifier=$identifier, metadata=$metadata)" + return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" } override fun equals(other: Any?): Boolean { @@ -182,7 +189,8 @@ internal constructor( } override fun hashCode(): Int { - var result = mainVersion.contentHashCode() + var result = major shl minor + result *= (patch ?: 1) result = 31 * result + (identifier?.hashCode() ?: 0) result = 31 * result + (metadata?.hashCode() ?: 0) return result From 1c909ae752a493c42041d062dd2d347ad165a997 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:15:01 +0800 Subject: [PATCH 094/114] Better Requirement Rule --- .../internal/util/semver/RangeTokenReader.kt | 251 ++++++++++++++++++ .../util/{ => semver}/SemVersionInternal.kt | 36 ++- .../mamoe/mirai/console/util/SemVersion.kt | 2 +- .../mirai/console/util/TestSemVersion.kt | 30 ++- 4 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/{ => semver}/SemVersionInternal.kt (91%) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt new file mode 100644 index 000000000..00a0524b2 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt @@ -0,0 +1,251 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +@file:Suppress("MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.console.internal.util.semver + +import net.mamoe.mirai.console.util.SemVersion +import kotlin.math.max +import kotlin.math.min + +internal object RangeTokenReader { + enum class TokenType { + STRING, + + /* 左括号 */ + LEFT, + + /* 右括号 */ + RIGHT, + + /* || */ + OR, + + /* && */ + AND, + GROUP + } + + sealed class Token { + abstract val type: TokenType + abstract val value: String + abstract val position: Int + + class LeftBracket(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.LEFT + override val value: String get() = "{" + + override fun toString(): String = "LB{" + } + + class RightBracket(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.RIGHT + override val value: String get() = "}" + + override fun toString(): String = "RB}" + } + + class Or(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.OR + override val value: String get() = "||" + override fun toString(): String = "OR||" + } + + class And(override val position: Int) : Token() { + override val type: TokenType get() = TokenType.AND + override val value: String get() = "&&" + + override fun toString(): String = "AD&&" + } + + class Group(val values: List, override val position: Int) : Token() { + override val type: TokenType get() = TokenType.GROUP + override val value: String get() = "" + } + + class Raw(val source: String, val start: Int, val end: Int) : Token() { + override val value: String get() = source.substring(start, end) + override val position: Int + get() = start + override val type: TokenType get() = TokenType.STRING + + override fun toString(): String = "R:$value" + } + } + + fun parseToTokens(source: String): List = ArrayList( + max(source.length / 3, 16) + ).apply { + var index = 0 + var position = 0 + fun flushOld() { + if (position > index) { + val id = index + index = position + for (i in id until position) { + if (!source[i].isWhitespace()) { + add(Token.Raw(source, id, position)) + return + } + } + } + } + + val iterator = source.indices.iterator() + for (i in iterator) { + position = i + when (source[i]) { + '{' -> { + flushOld() + add(Token.LeftBracket(i)) + index = i + 1 + } + '|' -> { + if (source.getOrNull(i + 1) == '|') { + flushOld() + add(Token.Or(i)) + index = i + 2 + iterator.nextInt() + } + } + '&' -> { + if (source.getOrNull(i + 1) == '&') { + flushOld() + add(Token.And(i)) + index = i + 2 + iterator.nextInt() + } + } + '}' -> { + flushOld() + add(Token.RightBracket(i)) + index = i + 1 + } + } + } + position = source.length + flushOld() + } + + fun collect(source: String, tokens: Iterator, root: Boolean): List = ArrayList().apply { + tokens.forEach { token -> + if (token is Token.LeftBracket) { + add(Token.Group(collect(source, tokens, false), token.position)) + } else if (token is Token.RightBracket) { + if (root) { + throw IllegalArgumentException("Syntax error: Unexpected }, ${buildMsg(source, token.position)}") + } else { + return@apply + } + } else add(token) + } + if (!root) { + throw IllegalArgumentException("Syntax error: Excepted }, ${buildMsg(source, source.length)}") + } + } + + private fun buildMsg(source: String, position: Int): String { + val ed = min(position + 10, source.length) + val st = max(0, position - 10) + return buildString { + append('`') + if (st != 0) append("...") + append(source, st, ed) + if (ed != source.length) append("...") + append("` at ").append(position) + } + } + + fun check(source: String, tokens: Iterator, group: Token.Group?) { + if (!tokens.hasNext()) { + throw IllegalArgumentException("Syntax error: empty rule, ${buildMsg(source, group?.position ?: 0)}") + } + var type = false + do { + val next = tokens.next() + if (type) { + if (next is Token.Group || next is Token.Raw) { + throw IllegalArgumentException("Syntax error: Except logic but got expression, ${buildMsg(source, next.position)}") + } + } else { + if (next is Token.Or || next is Token.And) { + throw IllegalArgumentException("Syntax error: Except expression but got logic, ${buildMsg(source, next.position)}") + } + if (next is Token.Group) { + check(source, next.values.iterator(), next) + } + } + type = !type + } while (tokens.hasNext()) + if (!type) { + throw IllegalArgumentException("Syntax error: Except more expression, ${buildMsg(source, group?.values?.last()?.position ?: source.length)}") + } + } + + fun parse(source: String, token: Token): SemVersion.Requirement { + return when (token) { + is Token.Group -> { + if (token.values.size == 1) { + parse(source, token.values.first()) + } else { + val logic = token.values.asSequence().map { it.type }.filter { + it == TokenType.OR || it == TokenType.AND + }.toSet() + if (logic.size == 2) { + throw IllegalArgumentException("Syntax error: || and && cannot use in one group, ${buildMsg(source, token.position)}") + } + val rules = token.values.asSequence().filter { + it is Token.Raw || it is Token.Group + }.map { parse(source, it) }.toList() + when (logic.first()) { + TokenType.OR -> { + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean { + rules.forEach { if (it.test(version)) return true } + return false + } + } + } + TokenType.AND -> { + return object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean { + rules.forEach { if (!it.test(version)) return false } + return true + } + } + } + else -> throw AssertionError() + } + } + } + is Token.Raw -> SemVersionInternal.parseRule(token.value) + else -> throw AssertionError() + } + } + + fun StringBuilder.dump(prefix: String, token: Token) { + when (token) { + is Token.LeftBracket -> append("${prefix}LF {\n") + + is Token.RightBracket -> append("${prefix}LR }\n") + + is Token.Or -> append("${prefix}OR ||\n") + + is Token.And -> append("${prefix}AND &&\n") + is Token.Group -> { + append("${prefix}GROUP {\n") + token.values.forEach { dump("$prefix ", it) } + append("${prefix}}\n") + } + is Token.Raw -> append("${prefix}RAW ${token.value}\n") + } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt similarity index 91% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index 2cbe56ec4..afe950ce3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -8,12 +8,9 @@ * */ -/* - * @author Karlatemp - */ - -package net.mamoe.mirai.console.internal.util +package net.mamoe.mirai.console.internal.util.semver +import net.mamoe.mirai.console.internal.util.semver.RangeTokenReader.dump import net.mamoe.mirai.console.util.SemVersion import kotlin.math.max import kotlin.math.min @@ -80,8 +77,8 @@ internal object SemVersionInternal { } @JvmStatic - private fun String.parseRule(): SemVersion.Requirement { - val trimmed = trim() + internal fun parseRule(rule: String): SemVersion.Requirement { + val trimmed = rule.trim() if (directVersion.matches(trimmed)) { val parsed = SemVersion.invoke(trimmed) return object : SemVersion.Requirement { @@ -143,7 +140,7 @@ internal object SemVersionInternal { else -> error("operator=$operator, version=$version1") } } - throw IllegalArgumentException("Cannot parse $this") + throw IllegalArgumentException("Cannot parse $rule") } private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement { @@ -158,19 +155,16 @@ internal object SemVersionInternal { if (requirement.isBlank()) { throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") } - return requirement.split("||").map { - it.parseRule().withRule(it) - }.let { checks -> - if (checks.size == 1) return checks[0] - object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - checks.forEach { rule -> - if (rule.test(version)) return true - } - return false - } - }.withRule(requirement) - } + val tokens = RangeTokenReader.parseToTokens(requirement) + val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true) + RangeTokenReader.check(requirement, collected.iterator(), null) + return kotlin.runCatching { + RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement) + }.onFailure { error -> + throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString { + collected.forEach { dump("", it) } + }, error) + }.getOrThrow() } @JvmStatic diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index bed586514..de1d647f8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION import net.mamoe.mirai.console.internal.data.map -import net.mamoe.mirai.console.internal.util.SemVersionInternal +import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals import net.mamoe.mirai.console.util.SemVersion.Requirement diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index c7325c177..ac7975d51 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -48,6 +48,12 @@ internal class TestSemVersion { return this } + fun assertInvalid(requirement: String) { + kotlin.runCatching { + SemVersion.parseRangeRequirement(requirement) + }.onSuccess { assert(false) { requirement } } + } + fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement { assert(!test(version)) { version } return this @@ -59,6 +65,8 @@ internal class TestSemVersion { .assert("1.0").assert("1.1") .assert("1.5").assert("1.14514") .assertFalse("2.33") + SemVersion.parseRangeRequirement("2.0||1.2.x") + SemVersion.parseRangeRequirement("{2.0||1.2.x} && 1.1.0 &&1.2.3") SemVersion.parseRangeRequirement("2.0 || 1.2.x") .assert("2.0").assert("2.0.0") .assertFalse("2.1") @@ -79,9 +87,24 @@ internal class TestSemVersion { .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") - kotlin.runCatching { SemVersion.parseRangeRequirement("WPOXAXW") } - .onSuccess { assert(false) } + SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0") + .assertFalse("1.0.0") + .assert("0.8.0") + .assertFalse("0.9.0") + SemVersion.parseRangeRequirement("{>= 1.0.0 && <= 1.2.3} || {>= 2.0.0 && <= 2.2.3}") + .assertFalse("1.3.0") + .assert("1.0.0").assert("1.2.3") + .assertFalse("0.9.0") + .assert("2.0.0").assert("2.2.3").assertFalse("2.3.4") + + assertInvalid("WPOXAXW") + assertInvalid("1.0.0 || 1.0.0 && 1.0.0") + assertInvalid("{") + assertInvalid("}") + assertInvalid("") + assertInvalid("1.5.78 &&") + assertInvalid("|| 1.0.0") } private fun String.check() { @@ -112,8 +135,9 @@ internal class TestSemVersion { "5.1+68-7".check() "5.1+68-".check() } + @Test - internal fun testSemVersionOfficial(){ + internal fun testSemVersionOfficial() { """ 1.0-RC 0.0.4 From 00781c215a15dcaf8fa23fe84d9b01cf49c32822 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:31:12 +0800 Subject: [PATCH 095/114] Update Math interval --- .../util/semver/SemVersionInternal.kt | 31 ++++++++++++++----- .../mirai/console/util/TestSemVersion.kt | 14 ++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index afe950ce3..f71be72f6 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -19,9 +19,8 @@ import kotlin.math.min internal object SemVersionInternal { private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() - private val versionRange = """([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\-\s*([0-9]+(\.[0-9]+)+(|[\-+].+))""".toRegex() private val versionMathRange = - """\[([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))\]""".toRegex() + """([\[\(])([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 = @@ -95,17 +94,35 @@ internal object SemVersionInternal { override fun test(version: SemVersion): Boolean = regex.matches(version.toString()) } } - (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range -> - var start = SemVersion.invoke(range.groupValues[1]) - var end = SemVersion.invoke(range.groupValues[4]) + versionMathRange.matchEntire(trimmed)?.let { range -> + // 1 mode + // 2 first + // 5 sec + // 8 type + var typeStart = range.groupValues[1][0] + var typeEnd = range.groupValues[8][0] + var start = SemVersion.invoke(range.groupValues[2]) + var end = SemVersion.invoke(range.groupValues[5]) if (start > end) { val c = end end = start start = c + val x = typeEnd + typeEnd = typeStart + typeStart = x + } + val a: (SemVersion) -> Boolean = when (typeStart) { + '[', ']' -> ({ start <= it }) + '(', ')' -> ({ start < it }) + else -> throw AssertionError() + } + val b: (SemVersion) -> Boolean = when (typeEnd) { + '[', ']' -> ({ it <= end }) + '(', ')' -> ({ it < end }) + else -> throw AssertionError() } - val compareRange = start..end return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean = version in compareRange + override fun test(version: SemVersion): Boolean = a(version) && b(version) } } versionRule.matchEntire(trimmed)?.let { result -> diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index ac7975d51..e7de240c9 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -72,14 +72,17 @@ internal class TestSemVersion { .assertFalse("2.1") .assert("1.2.5").assert("1.2.0").assertFalse("1.2") .assertFalse("1.0.0") - SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919") - .assert("1.0.0") - .assert("114.514").assert("114.514.1919") - .assertFalse("0.0.1") - .assertFalse("4444.4444") SemVersion.parseRangeRequirement("[1.0.0, 19190.0]") .assert("1.0.0").assertFalse("0.1.0") .assert("19190.0").assertFalse("19198.10") + SemVersion.parseRangeRequirement("[1.0.0, 2.0.0)") + .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(2.0.0, 1.0.0]") + .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(2.0.0, 1.0.0)") + .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") + SemVersion.parseRangeRequirement("(1.0.0, 2.0.0)") + .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement(" >= 1.0.0") .assert("1.0.0") .assert("114.514.1919") @@ -103,6 +106,7 @@ internal class TestSemVersion { assertInvalid("{") assertInvalid("}") assertInvalid("") + assertInvalid("1.2.3 - 3.2.1") assertInvalid("1.5.78 &&") assertInvalid("|| 1.0.0") } From 209bc97b32824b6e03805dc86a593c47a7953ecf Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:42:05 +0800 Subject: [PATCH 096/114] Update KDoc --- .../internal/util/semver/SemVersionInternal.kt | 9 +++++++-- .../net/mamoe/mirai/console/util/SemVersion.kt | 13 ++++++++++--- .../net/mamoe/mirai/console/util/TestSemVersion.kt | 5 +++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt index f71be72f6..d57c226e9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt @@ -21,7 +21,7 @@ internal object SemVersionInternal { 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 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() @@ -127,7 +127,7 @@ internal object SemVersionInternal { } versionRule.matchEntire(trimmed)?.let { result -> val operator = result.groupValues[1] - val version1 = SemVersion.invoke(result.groupValues[7]) + val version1 = SemVersion.invoke(result.groupValues[8]) return when (operator) { ">=" -> { object : SemVersion.Requirement { @@ -154,6 +154,11 @@ internal object SemVersionInternal { override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0 } } + "!=" -> { + object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0 + } + } else -> error("operator=$operator, version=$version1") } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt index de1d647f8..a80c467a2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt @@ -113,21 +113,28 @@ internal constructor( * * - `1.0.0-M4` 要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本 * - `1.x` 要求 1.x 版本 - * - `1.0.0 - 1.2.0` 要求 1.0.0 到 1.2.0 的任意版本, 注意 `-` 两边必须要有空格 - * - `[1.0.0, 1.2.0]` 与 `1.0.0 - 1.2.0` 一致 * - `> 1.0.0-RC` 要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC * - `>= 1.0.0-RC` 要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC * - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC * - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC + * - `!= 1.0.0-RC` 要求 除了1.0.0-RC 的任何版本 + * - `[1.0.0, 1.2.0]` + * - `(1.0.0, 1.2.0]` + * - `[1.0.0, 1.2.0)` + * - `(1.0.0, 1.2.0)` [数学区间](https://baike.baidu.com/item/%E5%8C%BA%E9%97%B4/1273117) * - * 对于多个规则, 也允许使用 `||` 拼接. + * 对于多个规则, 允许使用逻辑符号 `{}`, `||`, `&&` * 例如: * - `1.x || 2.x || 3.0.0` * - `<= 0.5.3 || >= 1.0.0` + * - `{> 1.0 && < 1.5} || {> 1.8}` + * - `{> 1.0 && < 1.5} || {> 1.8}` + * - `> 1.0.0 && != 1.2.0` * * 特别注意: * - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查 * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号 + * - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()` */ @Throws(IllegalArgumentException::class) @JvmStatic diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt index e7de240c9..3f74dd191 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt @@ -90,6 +90,11 @@ internal class TestSemVersion { .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") + SemVersion.parseRangeRequirement("!= 1.0.0 && != 2.0.0") + .assert("1.2.3").assert("2.1.1") + .assertFalse("1.0").assertFalse("1.0.0") + .assertFalse("2.0").assertFalse("2.0.0") + .assert("2.0.1").assert("1.0.1") SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0") .assertFalse("1.0.0") From dc0ba1d8ffdd27335cdc72ebbcdc51346b4e3c65 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 12:47:53 +0800 Subject: [PATCH 097/114] Fix SemVersionRangeRequirementBuilder --- .../console/util/SemVersionRangeRequirementBuilder.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt index 74145d1f8..be4b936ce 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt @@ -26,7 +26,7 @@ public object SemVersionRangeRequirementBuilder { } override fun toString(): String { - return "(${this@or}) or ($other)" + return "{${this@or}} || {$other}" } } } @@ -48,7 +48,7 @@ public object SemVersionRangeRequirementBuilder { } override fun toString(): String { - return "(${this@and}) or ($other)" + return "{${this@and}} && {$other}" } } } @@ -64,7 +64,12 @@ public object SemVersionRangeRequirementBuilder { @Suppress("NOTHING_TO_INLINE") @ILoveHim188moeForever - public inline fun custom(rule: SemVersion.Requirement): SemVersion.Requirement = rule + public fun custom(rule: (SemVersion) -> Boolean): SemVersion.Requirement = object : SemVersion.Requirement { + override fun test(version: SemVersion): Boolean = rule(version) + override fun toString(): String { + return "Custom{$rule}" + } + } /** * 标注一个 [SemVersionRangeRequirementBuilder] DSL From 2a6d98ba168d7ca025d33b2a796f8caa7a0360ee Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 21 Sep 2020 13:22:37 +0800 Subject: [PATCH 098/114] Add shadowJarMd5 for shadowed files --- .../internal/MiraiConsoleBuildConstants.kt | 2 +- buildSrc/src/main/kotlin/PublishingHelpers.kt | 74 +++++++++++++++++++ buildSrc/src/main/kotlin/Versions.kt | 2 +- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index f314be39c..95306d28e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -14,7 +14,7 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1600596035) + val buildDate: Instant = Instant.ofEpochSecond(1600663022) @JvmStatic val version: SemVersion = SemVersion("1.0-RC-dev-28") diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index 69b954dac..f7b2821c0 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -1,5 +1,6 @@ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "NOTHING_TO_INLINE", "RemoveRedundantBackticks") +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication @@ -7,6 +8,10 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.* import upload.Bintray +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.security.MessageDigest import java.util.* import kotlin.reflect.KProperty @@ -51,6 +56,44 @@ internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publi (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure) +fun InputStream.md5(): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + use { input -> + object : OutputStream() { + override fun write(b: Int) { + digest.update(b.toByte()) + } + }.use { output -> + input.copyTo(output) + } + } + return digest.digest() +} + +@OptIn(ExperimentalUnsignedTypes::class) +@JvmOverloads +fun ByteArray.toUHexString( + separator: String = " ", + offset: Int = 0, + length: Int = this.size - offset +): String { + if (length == 0) { + return "" + } + val lastIndex = offset + length + return buildString(length * 2) { + this@toUHexString.forEachIndexed { index, it -> + if (index in offset until lastIndex) { + var ret = it.toUByte().toString(16).toUpperCase() + if (ret.length == 1) ret = "0$ret" + append(ret) + if (index < lastIndex - 1) append(separator) + } + } + } +} + inline fun Project.setupPublishing( artifactId: String, bintrayRepo: String = "mirai", @@ -66,6 +109,32 @@ inline fun Project.setupPublishing( } } + afterEvaluate { + + val shadowJar = tasks.filterIsInstance().firstOrNull() ?: return@afterEvaluate + + tasks.register("shadowJarMd5") { + val outFiles = shadowJar.outputs.files.map { file -> + File(file.parentFile, file.name.removeSuffix(".jar").removeSuffix("-all") + "-all.jar.md5") + } + + outFiles.forEach { file -> + outputs.files(file) + } + + doLast { + for (file in outFiles) { + file + .also { it.createNewFile() } + .writeText(file.inputStream().md5().toUHexString().trim(Char::isWhitespace)) + } + } + + tasks.getByName("publish").dependsOn(this) + tasks.getByName("bintrayUpload").dependsOn(this) + } + } + if (Bintray.isBintrayAvailable(project)) { bintray { val keyProps = Properties() @@ -104,6 +173,11 @@ inline fun Project.setupPublishing( publications { register("mavenJava", MavenPublication::class) { from(components["java"]) + afterEvaluate { + for (file in tasks.getByName("shadowJarMd5").outputs.files) { + artifact(provider { file }) + } + } groupId = rootProject.group.toString() this.artifactId = artifactId diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index ef3a362e1..801f93344 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-28" + const val console = "1.0-RC-dev-29" const val consoleGraphical = "0.0.7" const val consoleTerminal = console From 2520e4bb35827b746c94068a209647adc38baef6 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Mon, 21 Sep 2020 18:09:01 +0800 Subject: [PATCH 099/114] delete SemVersionRangeRequirementBuilder --- .../plugin/jvm/JvmPluginDescription.kt | 26 ----- .../util/SemVersionRangeRequirementBuilder.kt | 95 ------------------- 2 files changed, 121 deletions(-) delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 736517b52..954d19919 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.util.SemVersion -import net.mamoe.mirai.console.util.SemVersionRangeRequirementBuilder /** * JVM 插件的描述. 通常作为 `plugin.yml` @@ -187,31 +186,6 @@ public class JvmPluginDescriptionBuilder( this.dependencies.add(PluginDependency(pluginId, null, isOptional)) } - /** - * 示例: - * - * ``` - * dependsOn("org.example.test-plugin") { "1.0.0".."1.2.0" } - * dependsOn("org.example.test-plugin") { npmPattern("1.x || >=2.5.0 || 5.0.0 - 7.2.3") } - * dependsOn("org.example.test-plugin") { ivyPattern("[1.0,2.0[") } - * dependsOn("org.example.test-plugin") { custom { it.toString() == "1.0.0" } } - * ``` - * - * @see PluginDependency - * @see SemVersionRangeRequirementBuilder - */ - @ILoveKuriyamaMiraiForever - public fun dependsOn( - @ResolveContext(PLUGIN_ID) pluginId: String, - isOptional: Boolean = false, - versionRequirement: SemVersionRangeRequirementBuilder.() -> SemVersion.Requirement, - ): JvmPluginDescriptionBuilder = - apply { - this.dependencies.add(PluginDependency(pluginId, - SemVersionRangeRequirementBuilder.run(versionRequirement), - isOptional)) - } - @Suppress("DEPRECATION_ERROR") public fun build(): JvmPluginDescription = diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt deleted file mode 100644 index be4b936ce..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - * - */ - -package net.mamoe.mirai.console.util - -/** - * 构造 [SemVersion.Requirement] 的 DSL - */ -public object SemVersionRangeRequirementBuilder { - /** @see [SemVersion.parseRangeRequirement] */ - @ILoveHim188moeForever - public fun parse(rule: String): SemVersion.Requirement = SemVersion.parseRangeRequirement(rule) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.or(other: SemVersion.Requirement): SemVersion.Requirement { - return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@or.test(version) || other.test(version) - } - - override fun toString(): String { - return "{${this@or}} || {$other}" - } - } - } - - @ILoveHim188moeForever - public infix fun String.or(other: String): SemVersion.Requirement = parse(this) or parse(other) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.or(other: String): SemVersion.Requirement = or(parse(other)) - - @ILoveHim188moeForever - public infix fun String.or(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) or other - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.and(other: SemVersion.Requirement): SemVersion.Requirement { - return object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean { - return this@and.test(version) && other.test(version) - } - - override fun toString(): String { - return "{${this@and}} && {$other}" - } - } - } - - @ILoveHim188moeForever - public infix fun String.and(other: String): SemVersion.Requirement = parse(this) and parse(other) - - @ILoveHim188moeForever - public infix fun SemVersion.Requirement.and(other: String): SemVersion.Requirement = and(parse(other)) - - @ILoveHim188moeForever - public infix fun String.and(other: SemVersion.Requirement): SemVersion.Requirement = parse(this) and other - - @Suppress("NOTHING_TO_INLINE") - @ILoveHim188moeForever - public fun custom(rule: (SemVersion) -> Boolean): SemVersion.Requirement = object : SemVersion.Requirement { - override fun test(version: SemVersion): Boolean = rule(version) - override fun toString(): String { - return "Custom{$rule}" - } - } - - /** - * 标注一个 [SemVersionRangeRequirementBuilder] DSL - */ - @Suppress("SpellCheckingInspection") - @Retention(AnnotationRetention.BINARY) - @DslMarker - internal annotation class ILoveHim188moeForever - - /** [SemVersionRangeRequirementBuilder] 的使用示例 */ - @Suppress("unused") - private class ExampleOfBuilder { - val e1 = SemVersionRangeRequirementBuilder.run { - "1.0.0" or "1.1.5" - } - val e2 = SemVersionRangeRequirementBuilder.run { - parse("> 1.0.0") and parse("< 1.2.3") - } - val e3 = SemVersionRangeRequirementBuilder.run { - ("> 1.0.0" and "< 1.2.3") or "2.0.0" - } - } -} \ No newline at end of file From b21188f9e149759663991f4c98809adb1539f1d2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 23 Sep 2020 10:08:59 +0800 Subject: [PATCH 100/114] Add project icon --- .idea/icon.png | Bin 0 -> 31103 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .idea/icon.png diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c872c7a5d945f42daf8ad0e3410ed4d0cbe9738d GIT binary patch literal 31103 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 literal 0 HcmV?d00001 From e478f1025020b290c28b8c1be1836e0245aac6c7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Sep 2020 07:41:09 +0800 Subject: [PATCH 101/114] Update Run.md --- docs/Run.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Run.md b/docs/Run.md index c4651e4bf..2e794c54f 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -2,9 +2,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 -## 使用第三方工具自动独立启动 +## 使用工具自动独立启动 -https://github.com/LXY1226/MiraiOK +官方: https://github.com/iTXTech/mirai-console-loader +第三方: https://github.com/LXY1226/MiraiOK ## 手动配置独立启动 From 1425594a70764705cb8b3d3ea0a01d088faf3127 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Sep 2020 14:01:47 +0800 Subject: [PATCH 102/114] Remove md5 --- buildSrc/src/main/kotlin/PublishingHelpers.kt | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index f7b2821c0..95e16283a 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -1,6 +1,5 @@ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "NOTHING_TO_INLINE", "RemoveRedundantBackticks") -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication @@ -8,7 +7,6 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.* import upload.Bintray -import java.io.File import java.io.InputStream import java.io.OutputStream import java.security.MessageDigest @@ -109,32 +107,6 @@ inline fun Project.setupPublishing( } } - afterEvaluate { - - val shadowJar = tasks.filterIsInstance().firstOrNull() ?: return@afterEvaluate - - tasks.register("shadowJarMd5") { - val outFiles = shadowJar.outputs.files.map { file -> - File(file.parentFile, file.name.removeSuffix(".jar").removeSuffix("-all") + "-all.jar.md5") - } - - outFiles.forEach { file -> - outputs.files(file) - } - - doLast { - for (file in outFiles) { - file - .also { it.createNewFile() } - .writeText(file.inputStream().md5().toUHexString().trim(Char::isWhitespace)) - } - } - - tasks.getByName("publish").dependsOn(this) - tasks.getByName("bintrayUpload").dependsOn(this) - } - } - if (Bintray.isBintrayAvailable(project)) { bintray { val keyProps = Properties() @@ -173,11 +145,6 @@ inline fun Project.setupPublishing( publications { register("mavenJava", MavenPublication::class) { from(components["java"]) - afterEvaluate { - for (file in tasks.getByName("shadowJarMd5").outputs.files) { - artifact(provider { file }) - } - } groupId = rootProject.group.toString() this.artifactId = artifactId From c712824048264ac0f6665d638c0b77f59923bad2 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Sep 2020 14:01:58 +0800 Subject: [PATCH 103/114] Update docs --- docs/ConfiguringProjects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index 6cd90981c..3fa2db93f 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -40,7 +40,7 @@ Mirai 鼓励插件开发者将自己的作品开源,并为此提供了模板 ### 使用 Gradle 插件配置项目 -`VERSION` 可在 +`VERSION`: [选择版本](#选择版本) 若使用 `build.gradle.kts`: ```kotlin From 5ef8cec447a8d5d61c818f4ab34b59f914c1e327 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 26 Sep 2020 23:51:00 +0800 Subject: [PATCH 104/114] Fix Terminal version rendering. --- backend/mirai-console/build.gradle.kts | 4 ++-- .../mirai/console/internal/MiraiConsoleBuildConstants.kt | 5 +++-- .../console/terminal/MiraiConsoleImplementationTerminal.kt | 4 +++- .../mirai/console/terminal/MiraiConsoleTerminalLoader.kt | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 3bb13026b..4795a996c 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -100,8 +100,8 @@ tasks { })""" } .replace( - Regex("""val version: SemVersion = SemVersion.parse\(".*"\)""") - ) { """val version: SemVersion = SemVersion.parse("${project.version}")""" } + Regex("""const val versionConst:\s+String\s+=\s+".*"""") + ) { """const val versionConst: String = "${project.version}"""" } ) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt index 95306d28e..b3abe5cbd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,9 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1600663022) + val buildDate: Instant = Instant.ofEpochSecond(1601134282) + const val versionConst: String = "1.0-RC-dev-29" @JvmStatic - val version: SemVersion = SemVersion("1.0-RC-dev-28") + val version: SemVersion = SemVersion(versionConst) } diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt index c6d790a76..1cd89f853 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt @@ -151,7 +151,9 @@ val terminal: Terminal = run { private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { override val name: String get() = "Terminal" override val vendor: String get() = "Mamoe Technologies" - override val version: SemVersion = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version + // net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version + // is console's version not frontend's version + override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst) } private val ANSI_RESET = Ansi().reset().toString() diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt index 2aa23e0b6..06bdaf650 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt @@ -59,7 +59,8 @@ object MiraiConsoleTerminalLoader { @ConsoleTerminalExperimentalApi fun printHelpMessage() { val help = listOf( - "" to "Mirai-Console[Terminal FrontEnd] v" + kotlin.runCatching { + "" to "Mirai-Console[Terminal FrontEnd] v" + net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst, + "" to " [ BackEnd] v" + kotlin.runCatching { net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version }.getOrElse { "" }, "" to "", From 5af02a4b7310c06dc66b916221f3e78df6e8ee55 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 27 Sep 2020 02:32:57 +0800 Subject: [PATCH 105/114] Remove gitVersion --- buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt | 6 ++++-- .../net/mamoe/mirai/console/gradle/VersionConstants.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt index b8567aaec..f18ecea8a 100644 --- a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt @@ -36,8 +36,8 @@ class MiraiConsoleBuildPlugin : Plugin { attributes( "Manifest-Version" to "1", "Implementation-Vendor" to "Mamoe Technologies", - "Implementation-Title" to target.name.toString(), - "Implementation-Version" to target.version.toString() + "+" + gitVersion + "Implementation-Title" to target.name, + "Implementation-Version" to target.version.toString() //+ "+" + gitVersion ) } @Suppress("UNCHECKED_CAST") @@ -120,6 +120,7 @@ fun Project.findLatestFile(): Pair { } ?: error("cannot find any file to upload")*/ } +/* val gitVersion: String by lazy { runCatching { val exec = Runtime.getRuntime().exec("git rev-parse HEAD") @@ -132,3 +133,4 @@ val gitVersion: String by lazy { return@lazy "UNKNOWN" }.getOrThrow() } +*/ \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt index 2db0c82e2..c4fc5e2bf 100644 --- a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.0-RC-dev-28" // value is written here automatically during build + const val CONSOLE_VERSION = "1.0-RC-dev-29" // value is written here automatically during build const val CORE_VERSION = "1.3.0" // value is written here automatically during build } \ No newline at end of file From 0d21ee4c1b7d1df1001f44cff13bc7b801e8ba43 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 27 Sep 2020 13:01:16 +0800 Subject: [PATCH 106/114] Fix stdout encoding --- .../mirai/console/terminal/MiraiConsoleTerminalLoader.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt index 06bdaf650..8436da087 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt @@ -172,14 +172,18 @@ internal fun overrideSTD() { PrintStream( BufferedOutputStream( logger = DefaultLogger("stdout").run { ({ line: String? -> info(line) }) } - ) + ), + false, + Charsets.UTF_8 ) ) System.setErr( PrintStream( BufferedOutputStream( logger = DefaultLogger("stderr").run { ({ line: String? -> warning(line) }) } - ) + ), + false, + Charsets.UTF_8 ) ) } From 7a2b6e721cb0a93d96565ef645a3e65c9dde9ae2 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 27 Sep 2020 13:16:45 +0800 Subject: [PATCH 107/114] Fix build --- .../mirai/console/terminal/MiraiConsoleTerminalLoader.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt index 8436da087..930e72b48 100644 --- a/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt +++ b/frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt @@ -174,7 +174,7 @@ internal fun overrideSTD() { logger = DefaultLogger("stdout").run { ({ line: String? -> info(line) }) } ), false, - Charsets.UTF_8 + "UTF-8" ) ) System.setErr( @@ -183,7 +183,7 @@ internal fun overrideSTD() { logger = DefaultLogger("stderr").run { ({ line: String? -> warning(line) }) } ), false, - Charsets.UTF_8 + "UTF-8" ) ) } From e214a430433eeb40bc1b8e926829b724087077ff Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 2 Oct 2020 13:45:25 +0800 Subject: [PATCH 108/114] Fix IDE resolve on startup --- buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt | 3 +-- .../run/projects/test-project/build.gradle.kts | 2 +- .../net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt index f18ecea8a..8dbd47d8a 100644 --- a/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt +++ b/buildSrc/src/main/kotlin/MiraiConsoleBuildPlugin.kt @@ -127,8 +127,7 @@ val gitVersion: String by lazy { exec.waitFor() exec.inputStream.readBytes().toString(Charsets.UTF_8).trim().also { println("Git commit id: $it") - } - }.onFailure { + } }.onFailure { it.printStackTrace() return@lazy "UNKNOWN" }.getOrThrow() diff --git a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts index 72f695b95..e1c0ff8b7 100644 --- a/tools/intellij-plugin/run/projects/test-project/build.gradle.kts +++ b/tools/intellij-plugin/run/projects/test-project/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { compileOnly(kotlin("stdlib-jdk8")) val core = "1.3.0" - val console = "1.0-RC-dev-4" + val console = "1.0-RC-dev-28" compileOnly("net.mamoe:mirai-console:$console") compileOnly("net.mamoe:mirai-core:$core") diff --git a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt index a03640687..723da4408 100644 --- a/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt +++ b/tools/intellij-plugin/src/main/kotlin/net/mamoe/mirai/console/intellij/resolve/resolveIdea.kt @@ -19,7 +19,6 @@ import net.mamoe.mirai.console.compiler.common.resolve.findParent import org.jetbrains.kotlin.descriptors.CallableDescriptor 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 @@ -98,7 +97,7 @@ fun KtDeclaration.resolveAllCallsWithElement(bindingContext: BindingContext): Se .filterIsInstance() .mapNotNull { val callee = it.calleeExpression ?: return@mapNotNull null - val resolved = callee.getResolvedCallOrResolveToCall(bindingContext) ?: return@mapNotNull null + val resolved = callee.getResolvedCall(bindingContext) ?: return@mapNotNull null resolved to it } @@ -128,7 +127,7 @@ fun KtElement?.getResolvedCallOrResolveToCall( context: BindingContext, bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL, ): ResolvedCall? { - return this?.getCall(context)?.getResolvedCall(context) ?: this?.resolveToCall(bodyResolveMode) + return this?.getCall(context)?.getResolvedCall(context)// ?: this?.resolveToCall(bodyResolveMode) } val ResolvedCall.valueParameters: List get() = this.resultingDescriptor.valueParameters From 48efa5a469546806c077936af719f6d713e32ca5 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 2 Oct 2020 13:47:22 +0800 Subject: [PATCH 109/114] 1.0-RC-dev-30 --- buildSrc/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 801f93344..181c9d0da 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.3.0" - const val console = "1.0-RC-dev-29" + const val console = "1.0-RC-dev-30" const val consoleGraphical = "0.0.7" const val consoleTerminal = console From b1bb417388d56d8087e45ab2b622ca47e81377ff Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 4 Oct 2020 22:28:56 +0800 Subject: [PATCH 110/114] Bump versions --- buildSrc/src/main/kotlin/Versions.kt | 2 +- .../kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt | 2 +- tools/intellij-plugin/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 181c9d0da..d5f4b7ac6 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -26,6 +26,6 @@ object Versions { const val bintray = "1.8.5" - const val blockingBridge = "1.0.5" + const val blockingBridge = "1.1.0" const val yamlkt = "0.5.3" } \ No newline at end of file diff --git a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt index c4fc5e2bf..6c293ee10 100644 --- a/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt +++ b/tools/gradle-plugin/src/main/kotlin/net/mamoe/mirai/console/gradle/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.0-RC-dev-29" // value is written here automatically during build + const val CONSOLE_VERSION = "1.0-RC-dev-30" // value is written here automatically during build const val CORE_VERSION = "1.3.0" // value is written here automatically during build } \ No newline at end of file diff --git a/tools/intellij-plugin/build.gradle.kts b/tools/intellij-plugin/build.gradle.kts index 93a94e3f8..2c02d5f7c 100644 --- a/tools/intellij-plugin/build.gradle.kts +++ b/tools/intellij-plugin/build.gradle.kts @@ -52,7 +52,7 @@ tasks.withType { sinceBuild("193.*") untilBuild("205.*") pluginDescription(""" - Plugin development support for Mirai Conosle + Plugin development support for Mirai Console

Features

    From ab5f9f5ceecb4628999b1defaf99facd9d268677 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Wed, 7 Oct 2020 13:25:14 +0800 Subject: [PATCH 111/114] Transfer `allocatePermissionIdForPlugin` to member function --- .../internal/plugin/JvmPluginInternal.kt | 4 +-- .../mirai/console/permission/PermissionId.kt | 7 +++++ .../console/permission/PermissionService.kt | 28 +++++++++++++++++-- .../console/plugin/jvm/AbstractJvmPlugin.kt | 4 ++- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index 9cd06244f..9284baaa1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -18,7 +18,6 @@ import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.permission.PermissionService.Companion.allocatePermissionIdForPlugin import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader @@ -26,7 +25,6 @@ import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceCont import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad -import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.NamedSupervisorJob import net.mamoe.mirai.utils.MiraiLogger import java.io.File @@ -50,7 +48,7 @@ internal abstract class JvmPluginInternal( final override val parentPermission: Permission by lazy { PermissionService.INSTANCE.register( - PermissionService.INSTANCE.allocatePermissionIdForPlugin(name, "*"), + PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.ROOT_PERMISSION), "The base permission" ) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt index 86fb7e3c3..9a92e8be2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt @@ -31,6 +31,13 @@ public data class PermissionId( @ResolveContext(PERMISSION_NAME) public val name: String, ) { init { + require(!namespace.contains(' ')) { + "' ' is not allowed in namespace" + } + require(!name.contains(' ')) { + "' ' is not allowed in id" + } + require(!namespace.contains(':')) { "':' is not allowed in namespace" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt index 88c55556d..1a12b8af1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt @@ -16,6 +16,8 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.internal.permission.checkType import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf +import net.mamoe.mirai.console.plugin.Plugin +import net.mamoe.mirai.console.plugin.name import kotlin.reflect.KClass /** @@ -89,6 +91,13 @@ public interface PermissionService

    { parent: Permission = RootPermission, ): P + /** 为 [Plugin] 分配一个 [PermissionId] */ + public fun allocatePermissionIdForPlugin( + plugin: Plugin, + @ResolveContext(COMMAND_NAME) permissionName: String, + reason: PluginPermissionIdRequestType + ): PermissionId = allocatePermissionIdForPluginDefaultImplement(plugin, permissionName, reason) + /////////////////////////////////////////////////////////////////////////// /** @@ -116,6 +125,15 @@ public interface PermissionService

    { @Throws(UnsupportedOperationException::class) public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) + /** [Plugin] 尝试分配的 [PermissionId] 来源 */ + public enum class PluginPermissionIdRequestType { + /** For [Plugin.parentPermission] */ + ROOT_PERMISSION, + + /** For [Plugin.permissionId] */ + PERMISSION_ID + } + public companion object { internal var instanceField: PermissionService<*>? = null @@ -131,8 +149,14 @@ public interface PermissionService

    { public fun

    PermissionService

    .getOrFail(id: PermissionId): P = get(id) ?: throw NoSuchElementException("Permission not found: $id") - internal fun PermissionService<*>.allocatePermissionIdForPlugin(pluginName: String, @ResolveContext(COMMAND_NAME) permissionName: String) = - PermissionId("plugin.${pluginName.toLowerCase()}", permissionName.toLowerCase()) + internal fun PermissionService<*>.allocatePermissionIdForPluginDefaultImplement( + plugin: Plugin, + @ResolveContext(COMMAND_NAME) permissionName: String, + reason: PluginPermissionIdRequestType + ) = PermissionId( + plugin.name.toLowerCase().replace(' ', '.'), + permissionName.toLowerCase().replace(' ', '.') + ) public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this] diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index 9ed8caa29..9b64d84f9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal import net.mamoe.mirai.console.permission.PermissionId +import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.minutesToMillis import net.mamoe.mirai.utils.secondsToMillis @@ -37,7 +38,8 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor( public final override val loader: JvmPluginLoader get() = super.loader - public final override fun permissionId(name: String): PermissionId = PermissionId(description.id, name) + public final override fun permissionId(name: String): PermissionId = + PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.PERMISSION_ID) /** * 重载 [PluginData] From b52ba9dc2c7803d9f14b1d658711d32b6cb70d45 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 9 Oct 2020 21:43:13 +0800 Subject: [PATCH 112/114] Fix typo --- .../main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt index d10324141..8830312bf 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt @@ -96,7 +96,7 @@ import kotlin.internal.LowPriorityInOverloadResolution */ public interface MessageScope { /** - * 如果此 [MessageScope], 仅包含一个消息对象, 则 [realTarget] 指向这个对象. + * 如果此 [MessageScope] 仅包含一个消息对象, 则 [realTarget] 指向这个对象. 否则 [realTarget] 为 `null`. * * 对于 [CommandSender] 作为 [MessageScope], [realTarget] 总是指令执行者 [User], 即 [CommandSender.user] * From b2d06aba14bed4fb731db43bf6fc9e293db72d0a Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 9 Oct 2020 21:43:42 +0800 Subject: [PATCH 113/114] Remove all `@JvmDefault` since `-Xjvm-default=all` mode --- .../mirai/console/MiraiConsoleFrontEndDescription.kt | 1 - .../net/mamoe/mirai/console/MiraiConsoleImplementation.kt | 2 -- .../net/mamoe/mirai/console/command/CommandSender.kt | 1 - .../console/command/description/CommandArgumentParser.kt | 1 - .../net/mamoe/mirai/console/data/PluginDataHolder.kt | 2 -- .../mamoe/mirai/console/plugin/PluginFileExtensions.kt | 8 -------- .../net/mamoe/mirai/console/plugin/ResourceContainer.kt | 2 -- .../kotlin/net/mamoe/mirai/console/util/MessageScope.kt | 1 - 8 files changed, 18 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt index 6b7426d4d..f8155a9d1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt @@ -43,6 +43,5 @@ public interface MiraiConsoleFrontEndDescription { /** * 返回显示在 [MiraiConsole] 启动时的信息 */ - @JvmDefault public fun render(): String = "Frontend ${name}: version ${version}, provided by $vendor" } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt index 94aee7ae3..a1fd6347a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -108,12 +108,10 @@ public interface MiraiConsoleImplementation : CoroutineScope { @JvmSynthetic - @JvmDefault public override suspend fun sendMessage(message: Message): Unit = withContext(Dispatchers.IO) { sendMessageJ(message) } @JvmSynthetic - @JvmDefault public override suspend fun sendMessage(message: String): Unit = withContext(Dispatchers.IO) { sendMessageJ(message) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index fa0aec048..08aaccac0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -171,7 +171,6 @@ public interface CommandSender : CoroutineScope, Permittee { * * 对于 [MemberCommandSender], 这个函数总是发送给所在群 */ - @JvmDefault @JvmBlockingBridge public suspend fun sendMessage(message: String): MessageReceipt? diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt index 3d717cbb6..08f7eee05 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt @@ -77,7 +77,6 @@ public interface CommandArgumentParser { * @see CommandArgumentParserException */ @Throws(CommandArgumentParserException::class) - @JvmDefault public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt index 06d655913..af2bae5db 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt @@ -41,7 +41,6 @@ public interface PluginDataHolder { * @see Companion.newPluginDataInstance * @see KClass.createType */ - @JvmDefault public fun newPluginDataInstance(type: KType): T = newPluginDataInstanceUsingReflection(type) as T @@ -64,7 +63,6 @@ public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope { /** * 仅支持确切的 [PluginData] 类型 */ - @JvmDefault public override fun newPluginDataInstance(type: KType): T { val classifier = type.classifier?.cast>() require(classifier != null && classifier.java == PluginData::class.java) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginFileExtensions.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginFileExtensions.kt index 276113880..459d768b4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginFileExtensions.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginFileExtensions.kt @@ -41,28 +41,24 @@ public interface PluginFileExtensions { * 从数据目录获取一个文件. * @see dataFolderPath */ - @JvmDefault public fun resolveDataFile(relativePath: String): File = dataFolderPath.resolve(relativePath).toFile() /** * 从数据目录获取一个文件. * @see dataFolderPath */ - @JvmDefault public fun resolveDataPath(relativePath: String): Path = dataFolderPath.resolve(relativePath) /** * 从数据目录获取一个文件. * @see dataFolderPath */ - @JvmDefault public fun resolveDataFile(relativePath: Path): File = dataFolderPath.resolve(relativePath).toFile() /** * 从数据目录获取一个文件路径. * @see dataFolderPath */ - @JvmDefault public fun resolveDataPath(relativePath: Path): Path = dataFolderPath.resolve(relativePath) @@ -83,27 +79,23 @@ public interface PluginFileExtensions { * 从配置目录获取一个文件. * @see configFolderPath */ - @JvmDefault public fun resolveConfigFile(relativePath: String): File = configFolderPath.resolve(relativePath).toFile() /** * 从配置目录获取一个文件. * @see configFolderPath */ - @JvmDefault public fun resolveConfigPath(relativePath: String): Path = configFolderPath.resolve(relativePath) /** * 从配置目录获取一个文件. * @see configFolderPath */ - @JvmDefault public fun resolveConfigFile(relativePath: Path): File = configFolderPath.resolve(relativePath).toFile() /** * 从配置目录获取一个文件路径. * @see configFolderPath */ - @JvmDefault public fun resolveConfigPath(relativePath: Path): Path = configFolderPath.resolve(relativePath) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/ResourceContainer.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/ResourceContainer.kt index e2e26cc0c..8937f4146 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/ResourceContainer.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/ResourceContainer.kt @@ -37,7 +37,6 @@ public interface ResourceContainer { * * @return 资源文件内容. 在未找到文件时返回 `null`. */ - @JvmDefault public fun getResource(path: String): String? = getResource(path, Charsets.UTF_8) /** @@ -45,7 +44,6 @@ public interface ResourceContainer { * * @return 资源文件内容. 在未找到文件时返回 `null`. */ - @JvmDefault public fun getResource(path: String, charset: Charset): String? = this.getResourceAsStream(path)?.use(InputStream::readBytes)?.let(::String) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt index 8830312bf..92a82a616 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt @@ -116,7 +116,6 @@ public interface MessageScope { /** * 立刻以此发送消息给所有在此 [MessageScope] 下的消息对象 */ - @JvmDefault @JvmBlockingBridge public suspend fun sendMessage(message: String) } From 5cdd32ab4caa84cc6fff125268751b691be54e50 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sat, 10 Oct 2020 11:36:23 +0800 Subject: [PATCH 114/114] Limit JvmPluginClassLoader resource loading region Fix #205 --- .../plugin/BuiltInJvmPluginLoaderImpl.kt | 4 +-- .../internal/plugin/JvmPluginClassLoader.kt | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt index ad335f874..d9acd38b8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt @@ -42,7 +42,7 @@ internal object BuiltInJvmPluginLoaderImpl : override val dataStorage: PluginDataStorage get() = MiraiConsoleImplementationBridge.dataStorageForJvmPluginLoader - internal val classLoaders: MutableList = mutableListOf() + internal val classLoaders: MutableList = mutableListOf() @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description @@ -68,7 +68,7 @@ internal object BuiltInJvmPluginLoaderImpl : val filePlugins = this.filterNot { pluginFileToInstanceMap.containsKey(it) }.associateWith { - URLClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader) + JvmPluginClassLoader(it, MiraiConsole::class.java.classLoader) }.onEach { (_, classLoader) -> classLoaders.add(classLoader) }.asSequence().findAllInstances().onEach { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt new file mode 100644 index 000000000..1787c3809 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt @@ -0,0 +1,27 @@ +/* + * 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 via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + * + */ + +package net.mamoe.mirai.console.internal.plugin + +import java.io.File +import java.net.URL +import java.net.URLClassLoader +import java.util.* + +internal class JvmPluginClassLoader( + file: File, + parent: ClassLoader?, +) : URLClassLoader(arrayOf(file.toURI().toURL()), parent) { + //// 只允许插件 getResource 时获取插件自身资源, #205 + override fun getResources(name: String?): Enumeration = findResources(name) + override fun getResource(name: String?): URL? = findResource(name) + // getResourceAsStream 在 URLClassLoader 中通过 getResource 确定资源 + // 因此无需 override getResourceAsStream +}