From 001fac65cc08a4da322c1847910964106e970955 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 18 Sep 2020 12:34:30 +0800 Subject: [PATCH] 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