From 966c9dae689e3047953618cd8f6f99138c31795b Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Wed, 5 Aug 2020 14:44:33 +0300 Subject: [PATCH] [Gradle, JS] Use not inverted visitor but straightforward visitor ^KT-41054 fixed --- .../kotlin/gradle/targets/js/npm/NpmRange.kt | 54 +++++++- .../targets/js/npm/NpmVersionConstraint.kt | 9 +- .../js/npm/NpmVersionInvertedVisitor.kt | 121 ++++++++++++++---- .../targets/js/npm/NpmVersionInverter.kt | 48 ++++++- .../kotlin/gradle/targets/js/npm/semver.kt | 4 +- 5 files changed, 189 insertions(+), 47 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmRange.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmRange.kt index 105326dd651..91352982b64 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmRange.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmRange.kt @@ -6,16 +6,25 @@ package org.jetbrains.kotlin.gradle.targets.js.npm import com.github.gundy.semver4j.model.Version +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmVersionInvertedVisitor.Companion.GT +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmVersionInvertedVisitor.Companion.GTEQ +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmVersionInvertedVisitor.Companion.LT +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmVersionInvertedVisitor.Companion.LTEQ data class NpmRange( val startVersion: SemVer? = null, val startInclusive: Boolean = false, val endVersion: SemVer? = null, val endInclusive: Boolean = false -) +) { + override fun toString(): String { + return "${if (startVersion != null) "${if (startInclusive) GTEQ else GT}$startVersion" else ""} ${if (endVersion != null) "${if (endInclusive) LTEQ else LT}$endVersion" else ""}" + + } +} val NONE_RANGE = NpmRange( - startVersion = Version.fromString(NONE_VERSION) + endVersion = Version.fromString(NONE_VERSION) .toSemVer() ) @@ -36,12 +45,49 @@ infix fun NpmRange.union(other: NpmRange): List { } } +fun NpmRange.invert(): List { + val result = mutableListOf() + if (startVersion != null || endVersion == null) { + result.add( + NpmRange( + endVersion = startVersion, + endInclusive = !startInclusive + ) + ) + } + + if (endVersion != null || startVersion == null) { + result.add( + NpmRange( + startVersion = endVersion, + startInclusive = !endInclusive + ) + ) + } + + return result.distinct() +} + +infix fun List.intersect(others: List): List = flatMap { current -> + others.mapNotNull { other -> + current intersect other + } +} + infix fun NpmRange.intersect(other: NpmRange): NpmRange? { if (!isIntersect(other)) return null - val startVersion = max(this.startVersion, other.startVersion) + val startVersion = when { + startVersion == null -> other.startVersion + other.startVersion == null -> startVersion + else -> max(this.startVersion, other.startVersion) + } - val endVersion = min(this.endVersion, other.endVersion) + val endVersion = when { + endVersion == null -> other.endVersion + other.endVersion == null -> endVersion + else -> min(this.endVersion, other.endVersion) + } return NpmRange( startVersion = startVersion, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionConstraint.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionConstraint.kt index 78b847852a7..aba2905051c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionConstraint.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionConstraint.kt @@ -31,14 +31,7 @@ class NpmVersionConstraint( ) } - val version = buildNpmVersion(listOf(requiredVersion), rejectedVersions) - - val requiredRange = "^$version" - return if (SemVer.valid(requiredRange) && SemVer.satisfies(version, requiredRange)) { - requiredRange - } else { - version - } + return buildNpmVersion(listOf(requiredVersion), rejectedVersions) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInvertedVisitor.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInvertedVisitor.kt index 5b5167a9fa1..a6101ee8106 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInvertedVisitor.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInvertedVisitor.kt @@ -10,17 +10,17 @@ import com.github.gundy.semver4j.generated.grammar.NodeSemverExpressionParser import com.github.gundy.semver4j.model.Version import org.antlr.v4.runtime.tree.TerminalNode -class NpmVersionInvertedVisitor : NodeSemverExpressionBaseVisitor() { - override fun visitEmptyRange(ctx: NodeSemverExpressionParser.EmptyRangeContext): NpmRange = - NONE_RANGE +class NpmVersionInvertedVisitor : NodeSemverExpressionBaseVisitor>() { + override fun visitEmptyRange(ctx: NodeSemverExpressionParser.EmptyRangeContext): List = + listOf(null) - override fun visitWildcard(ctx: NodeSemverExpressionParser.WildcardContext): NpmRange = - NONE_RANGE + override fun visitWildcard(ctx: NodeSemverExpressionParser.WildcardContext): List = + listOf(null) - override fun visitWildcardRange(ctx: NodeSemverExpressionParser.WildcardRangeContext): NpmRange = - inverseVersion(ctx.partialWildcardSemver().text) + override fun visitWildcardRange(ctx: NodeSemverExpressionParser.WildcardRangeContext): List = + listOf(eq(ctx.partialWildcardSemver().text)) - override fun visitOperator(ctx: NodeSemverExpressionParser.OperatorContext): NpmRange { + override fun visitOperator(ctx: NodeSemverExpressionParser.OperatorContext): List { val eq: TerminalNode? = ctx.unaryOperator().EQ() val gt: TerminalNode? = ctx.unaryOperator().GT() val lt: TerminalNode? = ctx.unaryOperator().LT() @@ -28,51 +28,120 @@ class NpmVersionInvertedVisitor : NodeSemverExpressionBaseVisitor() { val version = ctx.partialWildcardSemver().text if (gt == null && lt == null) { - return inverseVersion(version) + return listOf(eq(version)) } - val invertedEqToken = if (eq != null) EQ else "" + val eqToken = eq != null if (lt != null) { - return "$GT$invertedEqToken$version" + return listOf( + if (eqToken) lteq(version) else lt(version) + ) } - return "$LT$invertedEqToken$version" + return listOf( + if (eqToken) gteq(version) else gt(version) + ) } - override fun visitWildcardOperator(ctx: NodeSemverExpressionParser.WildcardOperatorContext): NpmRange = - NONE_RANGE + override fun visitWildcardOperator(ctx: NodeSemverExpressionParser.WildcardOperatorContext): List = + listOf(null) - override fun visitTildeRange(ctx: NodeSemverExpressionParser.TildeRangeContext): NpmRange { + override fun visitTildeRange(ctx: NodeSemverExpressionParser.TildeRangeContext): List { val version = Version.fromString(ctx.fullSemver().text) val nextVersion = version .incrementMinor() - return "$LT$version $OR $GTEQ$nextVersion" + return listOf(gteq(version)) } - override fun visitCaretRange(ctx: NodeSemverExpressionParser.CaretRangeContext): NpmRange { + override fun visitCaretRange(ctx: NodeSemverExpressionParser.CaretRangeContext): List { val version = Version.fromString(ctx.fullSemver().text) val nextVersion = version .incrementMajor() - return "$LT$version $OR $GTEQ$nextVersion" + return lt(version) union gteq(nextVersion) } - override fun visitFullySpecifiedSemver(ctx: NodeSemverExpressionParser.FullySpecifiedSemverContext): NpmRange = + override fun visitFullySpecifiedSemver(ctx: NodeSemverExpressionParser.FullySpecifiedSemverContext): List = inverseVersion(ctx.fullSemver().text) - override fun visitLogicalAndOfSimpleExpressions(ctx: NodeSemverExpressionParser.LogicalAndOfSimpleExpressionsContext): NpmRange = - ctx.simple() - .joinToString(OR) { visit(it) } + override fun visitLogicalAndOfSimpleExpressions(ctx: NodeSemverExpressionParser.LogicalAndOfSimpleExpressionsContext): List = + try { + ctx.simple() + .flatMap { visit(it) } + .filterNotNull() + .reduce { acc: NpmRange?, next: NpmRange -> + val npmRange = if (acc == null) null else acc intersect next + npmRange + } + } catch (e: UnsupportedOperationException) { + null + }.let { listOf(it) } - override fun visitLogicalOrOfMultipleRanges(ctx: NodeSemverExpressionParser.LogicalOrOfMultipleRangesContext): NpmRange = + override fun visitLogicalOrOfMultipleRanges(ctx: NodeSemverExpressionParser.LogicalOrOfMultipleRangesContext): List = ctx.basicRange() - .joinToString(AND) { visitBasicRange(it) } + .flatMap { visitBasicRange(it) } + .onEach { if (it == null) return listOf(null) } + .filterNotNull() + .fold(listOf()) { ranges, range -> + if (ranges.isEmpty()) listOf(range) + else ranges + .flatMap { it union range } - private fun inverseVersion(version: String): String = - "$GT$version $OR $LT$version" + } + private fun inverseVersion(version: String): List = + inverseVersion(Version.fromString(version)) + + private fun inverseVersion(version: Version): List = + gt(version) union lt(version) + + private fun lt(version: String): NpmRange = + lt(Version.fromString(version)) + + private fun lt(version: Version): NpmRange = + NpmRange( + endVersion = version.toSemVer(), + endInclusive = false + ) + + private fun lteq(version: String): NpmRange = + lteq(Version.fromString(version)) + + private fun lteq(version: Version): NpmRange = + NpmRange( + endVersion = version.toSemVer(), + endInclusive = true + ) + + private fun gt(version: String): NpmRange = + gt(Version.fromString(version)) + + private fun gt(version: Version): NpmRange = + NpmRange( + startVersion = version.toSemVer() + ) + + private fun gteq(version: String): NpmRange = + gteq(Version.fromString(version)) + + private fun gteq(version: Version): NpmRange = + NpmRange( + startVersion = version.toSemVer(), + startInclusive = true + ) + + private fun eq(version: String): NpmRange = + eq(Version.fromString(version)) + + private fun eq(version: Version): NpmRange = + NpmRange( + startVersion = version.toSemVer(), + startInclusive = true, + endVersion = version.toSemVer(), + endInclusive = true + ) companion object { const val EQ = "=" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInverter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInverter.kt index 88ce5dfa680..c9c33ffbba3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInverter.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmVersionInverter.kt @@ -9,9 +9,9 @@ import com.github.gundy.semver4j.generated.grammar.NodeSemverExpressionLexer import com.github.gundy.semver4j.generated.grammar.NodeSemverExpressionParser import org.antlr.v4.runtime.ANTLRInputStream import org.antlr.v4.runtime.CommonTokenStream -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmVersionInvertedVisitor.Companion.AND +import org.gradle.api.InvalidUserDataException -fun invertNpmVersion(version: String): String { +fun versionToNpmRanges(version: String): List { val lexer = NodeSemverExpressionLexer(ANTLRInputStream(version)) val tokens = CommonTokenStream(lexer) val parser = NodeSemverExpressionParser(tokens) @@ -22,8 +22,42 @@ fun invertNpmVersion(version: String): String { fun buildNpmVersion( includedVersions: List, excludedVersions: List -): String = - includedVersions - .joinToString(AND) { it.trimIndent() } + - excludedVersions - .joinToString(prefix = AND, separator = AND) { invertNpmVersion(it.trimIndent()) } \ No newline at end of file +): String { + val includedRange: NpmRange? = try { + includedVersions + .flatMap { versionToNpmRanges(it) } + .onEach { + if (it == null) { + throw InvalidUserDataException("Problem with parsing of included versions $includedVersions") + } + } + .filterNotNull() + .reduce { acc: NpmRange, next: NpmRange -> + val intersection = acc intersect next + requireNotNull(intersection) { + "Included versions have no intersection $includedVersions" + } + intersection!! + } + } catch (e: UnsupportedOperationException) { + throw InvalidUserDataException("No ranges for included versions $includedVersions") + } + + includedRange!! + + val excludedRanges: List = try { + excludedVersions + .flatMap { versionToNpmRanges(it) } + .mapNotNull { it?.invert() } + .reduce { acc, next -> acc intersect next } + } catch (e: UnsupportedOperationException) { + throw InvalidUserDataException("No ranges for excluded versions $excludedVersions") + } + + return if (excludedRanges.isEmpty()) { + includedRange.toString() + } else + excludedRanges + .mapNotNull { it intersect includedRange } + .joinToString(NpmVersionInvertedVisitor.OR) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/semver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/semver.kt index f1da8f567f8..100cd7d31e6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/semver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/semver.kt @@ -178,8 +178,8 @@ fun Version.toSemVer(): SemVer = major.toBigInteger(), minor.toBigInteger(), patch.toBigInteger(), - preRelease = preReleaseIdentifiers.joinToString("."), - build = buildIdentifiers.joinToString(".") + preRelease = preReleaseIdentifiers.joinToString(".").let { if (it.isNotEmpty()) it else null }, + build = buildIdentifiers.joinToString(".").let { if (it.isNotEmpty()) it else null } ) fun min(a: SemVer?, b: SemVer?): SemVer? {