From af19ac42edcfab5364b51e418ea7893e10b018a9 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Sat, 31 Jan 2026 14:42:39 +0100 Subject: [PATCH 01/13] docs: update documentation links --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 5151a9e..a2f9a61 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,7 @@ Maven Central Version - + Documentation @@ -96,7 +96,7 @@ Version.isOlder(currentVersion, latestVersion); // true ## 📚 Documentation - [Full Javadoc Documentation](https://javadoc.io/doc/io.github.milkdrinkers/javasemver) -- [Documentation](https://milkdrinkers.athyrium.eu/javasemver) +- [Documentation](https://docs.milkdrinkers.dev/javasemver) - [Maven Central](https://central.sonatype.com/artifact/io.github.milkdrinkers/javasemver) --- From 920211ded05d303a68b73ee0b5e02847046d005c Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Sat, 31 Jan 2026 15:23:24 +0100 Subject: [PATCH 02/13] docs: use upstream code of conduct --- docs/CODE_OF_CONDUCT.md | 132 ---------------------------------------- 1 file changed, 132 deletions(-) delete mode 100644 docs/CODE_OF_CONDUCT.md diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md deleted file mode 100644 index e148814..0000000 --- a/docs/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -GITHUB_CONTACT_EMAIL. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file From e7b208a9f3d33d2725a1843ea78173a5cd8e726f Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Sun, 9 Aug 2026 16:03:06 +0200 Subject: [PATCH 03/13] build: bump gradle wrapper version --- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++- gradlew | 18 +++++------- gradlew.bat | 36 ++++++++--------------- 4 files changed, 22 insertions(+), 36 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 40229 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 delta 35349 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 /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -173,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -206,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..a51ec4f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,30 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :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 %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 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! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% From 555f503707234f41ce656f7bb225fa6f9f1adc75 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 00:38:46 +0200 Subject: [PATCH 04/13] feat!: v2 version Fully featured java implementation of node-semvers features --- .../milkdrinkers/javasemver/Constraint.java | 197 ++++++++ .../github/milkdrinkers/javasemver/Range.java | 311 ++++++++++++ .../milkdrinkers/javasemver/Version.java | 467 +++++++++++++++--- .../javasemver/VersionCheckResult.java | 19 - .../javasemver/VersionCompare.java | 209 -------- .../javasemver/VersionParser.java | 72 --- .../javasemver/enums/Operator.java | 61 +++ .../javasemver/enums/ReleaseType.java | 48 ++ .../exception/RangeParseException.java | 27 + .../exception/VersionBuildException.java | 2 +- .../exception/VersionException.java | 2 +- .../exception/VersionParseException.java | 2 +- .../javasemver/internal/Coercion.java | 60 +++ .../javasemver/internal/Interval.java | 250 ++++++++++ .../javasemver/internal/RangeAlgebra.java | 433 ++++++++++++++++ .../javasemver/internal/RangeParsing.java | 379 ++++++++++++++ .../internal/VersionComparison.java | 142 ++++++ .../javasemver/internal/VersionParsing.java | 86 ++++ common/src/test/java/SemVerTest.java | 296 +++++------ 19 files changed, 2553 insertions(+), 510 deletions(-) create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/Range.java delete mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java delete mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java delete mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java create mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java new file mode 100644 index 0000000..ca9a7e4 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Constraint.java @@ -0,0 +1,197 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.Operator; +import io.github.milkdrinkers.javasemver.exception.RangeParseException; +import io.github.milkdrinkers.javasemver.internal.VersionComparison; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + * A single semver comparator e.g. {@code >=1.2.3} consisting of an {@link Operator} and a + * {@link Version}. Also supports wildcard comparator ({@code *} or {@code x}) which matches any version. + * + * @apiNote Implements {@link Predicate} so a constraint can be used directly as a filter e.g. {@code versions.stream().filter(Constraint.parse(">=1.2.3"))}. + */ +public class Constraint implements Predicate { + private final Operator operator; + private final Version version; + private final boolean any; + + private Constraint(Operator operator, Version version, boolean any) { + this.operator = operator; + this.version = version; + this.any = any; + } + + /** + * Creates a constraint from an explicit operator and version e.g. {@code new Constraint(Operator.GTE, someVersion)} to represent {@code >=1.2.3}. + * + * @param operator the comparison operator + * @param version the version to compare against + * @apiNote Intended for internal range-expansion logic (e.g. X-ranges, tilde/caret ranges) + * that needs to build constraints directly from already-parsed components rather than + * from a comparator string. + */ + public Constraint(@NotNull Operator operator, @NotNull Version version) { + this(Objects.requireNonNull(operator, "operator must not be null"), Objects.requireNonNull(version, "version must not be null"), false); + } + + /** + * Parses a single comparator string e.g. {@code >=1.2.3}, {@code 1.2.3} or {@code *}. + * + * @param input the comparator string to parse + * @return the parsed constraint + * @throws RangeParseException thrown if the string could not be parsed into a constraint + */ + public static @NotNull Constraint parse(String input) throws RangeParseException { + return parse(input, false); + } + + /** + * Parses a single comparator string e.g. {@code >=1.2.3}, {@code 1.2.3} or {@code *}. + * + * @param input the comparator string to parse + * @param loose whether the version part should be parsed leniently via {@link Version#parseLoose(String)} (tolerating a leading {@code v}/{@code =} and leading zeros) rather than strictly + * @return the parsed constraint + * @throws RangeParseException thrown if the string could not be parsed into a constraint + */ + public static @NotNull Constraint parse(String input, boolean loose) throws RangeParseException { + if (input == null) + throw new RangeParseException("Could not parse range constraint from: null"); + + final String trimmed = input.trim(); + + if (trimmed.isEmpty() || trimmed.equals("*") || trimmed.equalsIgnoreCase("x")) + return any(); + + Operator operator = null; + String versionPart = trimmed; + + if (trimmed.length() >= 2) { + final Operator twoChar = Operator.fromSymbol(trimmed.substring(0, 2)); + if (twoChar != null) { + operator = twoChar; + versionPart = trimmed.substring(2); + } + } + + if (operator == null) { + final Operator oneChar = Operator.fromSymbol(trimmed.substring(0, 1)); + if (oneChar != null) { + operator = oneChar; + versionPart = trimmed.substring(1); + } + } + + if (operator == null) + operator = Operator.EQ; + + try { + final Version parsedVersion = loose ? Version.parseLoose(versionPart.trim()) : Version.parse(versionPart.trim()); + return new Constraint(operator, parsedVersion, false); + } catch (RuntimeException e) { + throw new RangeParseException("Could not parse range constraint from: " + input, e); + } + } + + /** + * Creates a wildcard constraint, which matches any version. + * + * @return a wildcard constraint + */ + public static @NotNull Constraint any() { + return new Constraint(null, null, true); + } + + /** + * Checks whether a version satisfies this constraint. + * + * @param v the version to check + * @return true if the version satisfies this constraint + */ + public boolean contains(@NotNull Version v) { + if (any) + return true; + + final int comparison = VersionComparison.comparePrecedence(v, version); + + switch (operator) { + case GT: + return comparison > 0; + case GTE: + return comparison >= 0; + case LT: + return comparison < 0; + case LTE: + return comparison <= 0; + case EQ: + return comparison == 0; + default: + return false; + } + } + + /** + * Predicate implementation, delegating to {@link #contains(Version)}. + * + * @param v the version to test + * @return true if the version satisfies this constraint + */ + @Override + public boolean test(Version v) { + return contains(v); + } + + /** + * Gets the operator of this constraint. + * + * @return the operator, or {@code null} if this is the wildcard constraint + */ + public @Nullable Operator getOperator() { + return operator; + } + + /** + * Gets the version of this constraint. + * + * @return the version, or {@code null} if this is the wildcard constraint + */ + public @Nullable Version getVersion() { + return version; + } + + /** + * Checks whether this is the wildcard constraint. + * + * @return true if this constraint matches any version + */ + public boolean isAny() { + return any; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Constraint)) return false; + Constraint that = (Constraint) o; + return any == that.any + && operator == that.operator + && Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(operator, version, any); + } + + @Override + public String toString() { + if (any) + return "*"; + + return (operator == Operator.EQ ? "" : operator.getSymbol()) + version; + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Range.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Range.java new file mode 100644 index 0000000..ee3c88b --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Range.java @@ -0,0 +1,311 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.exception.RangeParseException; +import io.github.milkdrinkers.javasemver.internal.RangeAlgebra; +import io.github.milkdrinkers.javasemver.internal.RangeParsing; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Predicate; + +/** + * A semver range e.g. {@code >=1.2.3 <2.0.0 || 3.x}, consisting of one or more comparator sets + * joined by {@code ||} (OR), where each comparator set is one or more {@link Constraint}'s that + * must all match (AND). + * + * @apiNote Implements {@link Predicate} so a range can be used directly as a filter e.g. {@code versions.stream().filter(Range.parse(">=1.2.3"))}. + */ +public final class Range implements Predicate { + private final List> comparatorSets; + + private Range(List> comparatorSets) { + final List> copy = new ArrayList<>(); + for (List set : comparatorSets) + copy.add(Collections.unmodifiableList(new ArrayList<>(set))); + + this.comparatorSets = Collections.unmodifiableList(copy); + } + + /** + * Parses a range string e.g. {@code >=1.2.3 <2.0.0 || 3.x}. + * + * @param input the range string to parse + * @return the parsed range + * @throws RangeParseException thrown if the string could not be parsed into a range + */ + public static @NotNull Range parse(String input) throws RangeParseException { + return new Range(RangeParsing.parse(input)); + } + + /** + * Parses a range string leniently e.g. {@code >=v1.2.3 <2.0.0 || 3.x}, tolerating a leading {@code v}/{@code =} and leading zeros in each constraints version part. + * + * @param input the range string to parse + * @return the parsed range + * @throws RangeParseException thrown if the string could not be parsed into a range + * @apiNote Only the version part of primitive comparators (e.g. {@code >=v1.2.3}) is parsed leniently. X-ranges, tilde/caret and hyphen ranges already tolerate leading zeros. + */ + public static @NotNull Range parseLoose(String input) throws RangeParseException { + return new Range(RangeParsing.parse(input, true)); + } + + /** + * Parses a range string e.g. {@code >=1.2.3 <2.0.0 || 3.x}. + * + * @param input the range string to parse + * @return the parsed range wrapped in an optional, or an empty optional if parsing failed + */ + public static @NotNull Optional parseOptional(String input) { + try { + return Optional.of(parse(input)); + } catch (RuntimeException ignored) { + return Optional.empty(); + } + } + + /** + * Checks whether a range string is valid. + * + * @param input the range string to check + * @return whether the string could be parsed into a range + * @apiNote Uses {@link Range#parseOptional(String)} internally + */ + public static boolean isValid(String input) { + return parseOptional(input).isPresent(); + } + + /** + * Finds the highest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range to satisfy + * @return the highest satisfying version by natural order, or an empty optional if none satisfy the range + */ + public static @NotNull Optional maxSatisfying(Collection versions, Range range) { + return versions.stream() + .filter(range::contains) + .max(Comparator.naturalOrder()); + } + + /** + * Finds the highest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range string to parse and satisfy + * @return the highest satisfying version by natural order, or an empty optional if none satisfy the range + * @throws RangeParseException thrown if the range string could not be parsed + * @apiNote Delegates to {@link #parse(String)} to parse the range + */ + public static @NotNull Optional maxSatisfying(Collection versions, String range) { + return maxSatisfying(versions, parse(range)); + } + + /** + * Finds the lowest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range to satisfy + * @return the lowest satisfying version by natural order, or an empty optional if none satisfy the range + */ + public static @NotNull Optional minSatisfying(Collection versions, Range range) { + return versions.stream() + .filter(range::contains) + .min(Comparator.naturalOrder()); + } + + /** + * Finds the lowest version in a collection that satisfies a range. + * + * @param versions the candidate versions + * @param range the range string to parse and satisfy + * @return the lowest satisfying version by natural order, or an empty optional if none satisfy the range + * @throws RangeParseException thrown if the range string could not be parsed + * @apiNote Delegates to {@link #parse(String)} to parse the range + */ + public static @NotNull Optional minSatisfying(Collection versions, String range) { + return minSatisfying(versions, parse(range)); + } + + /** + * Checks whether a version satisfies this range. + *

+ * A version with a prerelease tag may only satisfy a comparator set if that set contains at least one constraint whose version also + * has a prerelease tag and shares the same {@code major.minor.patch} as the candidate. This + * keeps prerelease versions from matching ranges that don't mention that prerelease line. + * + * @param v the version to check + * @return true if the version satisfies at least one comparator set (OR), where a set is satisfied if the version satisfies all of its constraints (AND) and, when the version has a prerelease, the set opts into that prerelease line + */ + public boolean contains(@NotNull Version v) { + for (List comparatorSet : comparatorSets) { + if (!setAllowsPrerelease(comparatorSet, v)) + continue; + + boolean allMatch = true; + + for (Constraint constraint : comparatorSet) { + if (!constraint.contains(v)) { + allMatch = false; + break; + } + } + + if (allMatch) + return true; + } + + return false; + } + + /** + * Checks whether a comparator set is allowed to match a prerelease candidate. + *

+ * Stable candidates are always allowed through. A prerelease candidate is only allowed + * through a set if the set contains a constraint whose version also carries a prerelease tag + * and shares the candidates exact {@code major.minor.patch}. + * + * @param comparatorSet the comparator set (AND-group) being evaluated + * @param candidate the version being tested against the range + * @return true if the set may be used to evaluate the candidate + */ + private static boolean setAllowsPrerelease(@NotNull List comparatorSet, @NotNull Version candidate) { + if (!candidate.hasPreRelease()) + return true; + + for (Constraint constraint : comparatorSet) { + final Version constraintVersion = constraint.getVersion(); + + if (constraintVersion == null || !constraintVersion.hasPreRelease()) + continue; + + if (constraintVersion.getMajor() == candidate.getMajor() + && constraintVersion.getMinor() == candidate.getMinor() + && constraintVersion.getPatch() == candidate.getPatch()) + return true; + } + + return false; + } + + /** + * Predicate implementation, delegating to {@link #contains(Version)}. + * + * @param v the version to test + * @return true if the version satisfies this range + */ + @Override + public boolean test(Version v) { + return contains(v); + } + + /** + * Gets the comparator sets making up this range. + * + * @return the unmodifiable list of comparator sets, joined by OR, each containing constraints joined by AND + */ + public @NotNull List> getComparatorSets() { + return comparatorSets; + } + + /** + * Checks whether this range matches any version. + * + * @return true if any comparator set is empty or contains the wildcard constraint + */ + public boolean isAny() { + for (List comparatorSet : comparatorSets) { + if (comparatorSet.isEmpty()) + return true; + + for (Constraint constraint : comparatorSet) { + if (constraint.isAny()) + return true; + } + } + + return false; + } + + /** + * Finds the lowest version this range can possibly satisfy. + * + * @return the lowest satisfying version by natural order, or an empty optional if this range admits no version at all (e.g. every comparator set is contradictory) + * @apiNote Delegates to {@link RangeAlgebra#minVersion(Range)} + */ + public @NotNull Optional minVersion() { + return RangeAlgebra.minVersion(this); + } + + /** + * Checks whether this range and another range intersect, e.g. share at least one version they could both possibly match. + * + * @param other the other range + * @return true if there exists a version that could satisfy both this range and {@code other} + * @apiNote Delegates to {@link RangeAlgebra#intersects(Range, Range)} + */ + public boolean intersects(@NotNull Range other) { + return RangeAlgebra.intersects(this, other); + } + + /** + * Checks whether this range is a subset of another range, e.g. every version this range could possibly match is also matched by {@code dom}. + * + * @param dom the candidate superset (dominating) range + * @return true if every version this range could match, {@code dom} could also match + * @apiNote Delegates to {@link RangeAlgebra#isSubsetOf(Range, Range)} + */ + public boolean isSubsetOf(@NotNull Range dom) { + return RangeAlgebra.isSubsetOf(this, dom); + } + + /** + * Finds the shortest range that has the same membership as {@code range} over a supplied set of versions. + *

The interval is represented as a lower and an upper bound, each of which is either a + * {@link Version} paired with an inclusive/exclusive flag, or {@code null} meaning unbounded + * ({@code -} for the lower bound, {@code +} for the upper bound). This normalized form makes it + * cheap to test two AND-sets for overlap ({@link #overlaps(Interval, Interval)}) without having to + * reason about each constraint individually.

+ * + *

All version comparisons are done via {@link VersionComparison#comparePrecedence(Version, Version)}, e.g. build metadata is ignored, matching the rest of the range matching logic.

+ */ +@ApiStatus.Internal +public final class Interval { + private final Version lower; + private final boolean lowerInclusive; + private final Version upper; + private final boolean upperInclusive; + + private Interval(@Nullable Version lower, boolean lowerInclusive, @Nullable Version upper, boolean upperInclusive) { + this.lower = lower; + this.lowerInclusive = lowerInclusive; + this.upper = upper; + this.upperInclusive = upperInclusive; + } + + /** + * Combines an AND-set of constraints into the single interval that is their intersection. + * + *

Each constraint contributes a half bounded (or, for {@code EQ}, fully bounded) interval:

+ *
    + *
  • {@code any} → {@code (-, +)}, contributes nothing.
  • + *
  • {@code EQ v} → lower = {@code v} inclusive, upper = {@code v} inclusive.
  • + *
  • {@code GT v} → lower = {@code v} exclusive.
  • + *
  • {@code GTE v} → lower = {@code v} inclusive.
  • + *
  • {@code LT v} → upper = {@code v} exclusive.
  • + *
  • {@code LTE v} → upper = {@code v} inclusive.
  • + *
+ * + *

Each contribution is intersected into a running result that starts fully unbounded + * ({@code (-, +)}):

+ *
    + *
  • A candidate lower bound replaces the running lower bound only if it is strictly + * tighter, e.g. strictly greater (a {@code -} running lower bound is always beaten + * by any real version). If the candidate's version equals the running lower bound's version, + * the version is kept but the bound becomes inclusive only if both contributions + * were inclusive, the exclusive (tighter) side wins.
  • + *
  • Uppers are handled symmetrically: a candidate replaces the running upper bound only if + * it is strictly lesser, and ties keep the exclusive side.
  • + *
+ * + * @param andSet the AND-ed constraints to combine e.g. the constraints of one OR-part of a range + * @return the interval that is the intersection of all of {@code andSet}'s constraints + */ + public static @NotNull Interval of(@NotNull List andSet) { + Bound lower = new Bound(null, false); + Bound upper = new Bound(null, false); + + for (final Constraint constraint : andSet) { + if (constraint.isAny()) + continue; + + final Operator operator = constraint.getOperator(); + final Version version = constraint.getVersion(); + + switch (operator) { + case GT: + lower = lower.tightenLower(version, false); + break; + case GTE: + lower = lower.tightenLower(version, true); + break; + case LT: + upper = upper.tightenUpper(version, false); + break; + case LTE: + upper = upper.tightenUpper(version, true); + break; + case EQ: + lower = lower.tightenLower(version, true); + upper = upper.tightenUpper(version, true); + break; + default: + break; + } + } + + return new Interval(lower.version, lower.inclusive, upper.version, upper.inclusive); + } + + /** + * An immutable (version, inclusive) pair representing one side of a running interval bound, + * with {@code version == null} meaning unbounded. Kept as its own tiny type so that tightening + * a bound is a single atomic "replace with the new (version, inclusive) pair or keep the old + * one" decision, never a partial update where the version and inclusivity are computed + * against inconsistent snapshots of each other. + */ + private static final class Bound { + private final Version version; + private final boolean inclusive; + + private Bound(@Nullable Version version, boolean inclusive) { + this.version = version; + this.inclusive = inclusive; + } + + /** + * Intersects this lower bound with a candidate {@code (candidate, candidateInclusive)} lower bound, keeping whichever is strictly tighter (greater), or, on a tie, the exclusive one. + */ + private Bound tightenLower(@NotNull Version candidate, boolean candidateInclusive) { + if (this.version == null) + return new Bound(candidate, candidateInclusive); + + final int c = VersionComparison.comparePrecedence(candidate, this.version); + if (c > 0) + return new Bound(candidate, candidateInclusive); + if (c < 0) + return this; + + return new Bound(this.version, this.inclusive && candidateInclusive); + } + + /** + * Intersects this upper bound with a candidate {@code (candidate, candidateInclusive)} upper bound, keeping whichever is strictly tighter (lesser), or, on a tie, the exclusive one. Symmetric to {@link #tightenLower}. + */ + private Bound tightenUpper(@NotNull Version candidate, boolean candidateInclusive) { + if (this.version == null) + return new Bound(candidate, candidateInclusive); + + final int c = VersionComparison.comparePrecedence(candidate, this.version); + if (c < 0) + return new Bound(candidate, candidateInclusive); + if (c > 0) + return this; + + return new Bound(this.version, this.inclusive && candidateInclusive); + } + } + + /** + * Gets the lower bound of this interval. + * + * @return the lower bound, or {@code null} meaning unbounded ({@code -}) + */ + public @Nullable Version getLower() { + return lower; + } + + /** + * Checks whether the lower bound is inclusive. + * + * @return true if the lower bound (when non null) is included in the interval + */ + public boolean isLowerInclusive() { + return lowerInclusive; + } + + /** + * Gets the upper bound of this interval. + * + * @return the upper bound, or {@code null} meaning unbounded ({@code +}) + */ + public @Nullable Version getUpper() { + return upper; + } + + /** + * Checks whether the upper bound is inclusive. + * + * @return true if the upper bound (when non null) is included in the interval + */ + public boolean isUpperInclusive() { + return upperInclusive; + } + + /** + * Checks whether this interval contains no versions at all. + * + *

When both bounds are present: empty if {@code lower > upper}, or if {@code lower == upper} + * and either end is exclusive (a single point interval {@code [v, v]} is non empty only when + * both ends include {@code v}). If either bound is unbounded ({@code null}), the interval can + * never be empty on account of that side.

+ * + * @return true if this interval is empty + */ + public boolean isEmpty() { + if (lower == null || upper == null) + return false; + + final int c = VersionComparison.comparePrecedence(lower, upper); + if (c > 0) + return true; + if (c == 0) + return !(lowerInclusive && upperInclusive); + + return false; + } + + /** + * Checks whether two intervals overlap, e.g. share at least one version. + * + *

An empty interval never overlaps anything. Otherwise, two non empty intervals overlap iff + * each one's lower endpoint is at or before the other's upper endpoint, where endpoints are only + * allowed to "touch" (equal versions) if both sides that meet there actually include that point.

+ * + * @param a the first interval + * @param b the second interval + * @return true if {@code a} and {@code b} share at least one version + */ + public static boolean overlaps(@NotNull Interval a, @NotNull Interval b) { + if (a.isEmpty() || b.isEmpty()) + return false; + + return lowerLessOrEqualUpper(a.lower, a.lowerInclusive, b.upper, b.upperInclusive) + && lowerLessOrEqualUpper(b.lower, b.lowerInclusive, a.upper, a.upperInclusive); + } + + /** + * Checks whether a lower endpoint is less than or equal to an upper endpoint, where the endpoints are only allowed to be equal ("touch") if both sides include that point. + * + * @param lowerVersion the lower endpoint's version, or {@code null} for {@code -} + * @param lowerInclusive whether the lower endpoint includes {@code lowerVersion} + * @param upperVersion the upper endpoint's version, or {@code null} for {@code +} + * @param upperInclusive whether the upper endpoint includes {@code upperVersion} + * @return true if the lower endpoint is at or before the upper endpoint + */ + private static boolean lowerLessOrEqualUpper(@Nullable Version lowerVersion, boolean lowerInclusive, @Nullable Version upperVersion, boolean upperInclusive) { + if (lowerVersion == null || upperVersion == null) + return true; + + final int c = VersionComparison.comparePrecedence(lowerVersion, upperVersion); + if (c < 0) + return true; + if (c > 0) + return false; + + return lowerInclusive && upperInclusive; + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java new file mode 100644 index 0000000..ba50793 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeAlgebra.java @@ -0,0 +1,433 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Range; +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.Operator; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * Algebraic operations over {@link Range} built on top of the per comparator set {@link Interval} representation e.g. computing the lowest version a range can possibly admit. + */ +@ApiStatus.Internal +public final class RangeAlgebra { + private RangeAlgebra() { + } + + /** + * Finds the lowest version a range can possibly satisfy. + *

+ * Each comparator set (an AND-group joined by OR at the {@link Range} level) is reduced to an + * {@link Interval}. Empty intervals contribute nothing. For every other interval, the interval's + * own minimum is computed, {@code 0.0.0} for an unbounded ({@code -}) lower bound, the + * lower bound itself when inclusive, or the next version after it when exclusive, and kept + * only if the range as a whole actually admits it, since the interval's minimum can still be + * excluded by another AND-ed upper bound or by the range's prerelease rule. The least of the + * surviving candidates is the range's minimum. + * + * @param range the range to inspect + * @return the lowest satisfying version by natural order, or an empty optional if the range admits no version at all + */ + public static @NotNull Optional minVersion(@NotNull Range range) { + final List candidates = new ArrayList<>(); + + for (final List comparatorSet : range.getComparatorSets()) { + final Interval interval = Interval.of(comparatorSet); + if (interval.isEmpty()) + continue; + + final Version candidate = intervalMinimum(interval); + if (range.contains(candidate)) + candidates.add(candidate); + } + + return candidates.stream().min(Comparator.naturalOrder()); + } + + /** + * Checks whether two ranges intersect, e.g. share at least one version they could both possibly match. + *

+ * Each range is reduced to its list of non empty {@link Interval}s, one per comparator set + * (an AND-group joined by OR at the {@link Range} level). Comparator sets that reduce to an + * empty interval (contradictory AND-ed constraints) are dropped, since they can never + * contribute a version. The two ranges intersect iff some interval of {@code a} overlaps some interval of {@code b}. + * + * @param a the first range + * @param b the second range + * @return true if the ranges share at least one version they could both match + */ + public static boolean intersects(@NotNull Range a, @NotNull Range b) { + final List intervalsA = nonEmptyIntervals(a); + final List intervalsB = nonEmptyIntervals(b); + + for (final Interval intervalA : intervalsA) { + for (final Interval intervalB : intervalsB) { + if (Interval.overlaps(intervalA, intervalB)) + return true; + } + } + + return false; + } + + /** + * An unbounded lower bound comparator, standing in for the {@code *} wildcard. + */ + private static final Constraint GTE_ZERO = new Constraint(Operator.GTE, Version.of(0L, 0L, 0L)); + + /** + * Checks whether a version's precedence is greater than everything a range could possibly match. + * + * @param v the version to check + * @param range the range to check against + * @return true if {@code v} is above every version {@code range} could match + */ + public static boolean isAbove(@NotNull Version v, @NotNull Range range) { + return outside(v, range, true); + } + + /** + * Checks whether a version's precedence is less than everything a range could possibly match. + * + * @param v the version to check + * @param range the range to check against + * @return true if {@code v} is below every version {@code range} could match + */ + public static boolean isBelow(@NotNull Version v, @NotNull Range range) { + return outside(v, range, false); + } + + /** + * Whether {@code version} lies entirely above ({@code above == true}, e.g. {@code gtr}) or below ({@code above == false}, e.g. {@code ltr}) + * every version {@code range} could match. For each OR-ed comparator set the highest and + * lowest semver comparators are found, then the version is judged against those endpoints and their operators. + * + * @param version the version to check + * @param range the range to check against + * @param above {@code true} for {@code gtr}, {@code false} for {@code ltr} + * @return true if {@code version} is entirely outside {@code range} in the requested direction + */ + private static boolean outside(@NotNull Version version, @NotNull Range range, boolean above) { + if (range.contains(version)) + return false; + + for (final List set : range.getComparatorSets()) { + Constraint high = null; + Constraint low = null; + for (final Constraint raw : set) { + final Constraint c = raw.isAny() ? GTE_ZERO : raw; + if (high == null) { + high = c; + low = c; + } else if (edgeGreater(c.getVersion(), high.getVersion(), above)) { + high = c; + } else if (edgeLess(c.getVersion(), low.getVersion(), above)) { + low = c; + } + } + + if (high == null) + continue; + + final Operator hop = high.getOperator(); + final Operator lop = low.getOperator(); + + if (above) { + if (hop == Operator.GT || hop == Operator.GTE) + return false; + if ((lop == Operator.EQ || lop == Operator.GT) && lte(version, low.getVersion())) + return false; + else if (lop == Operator.GTE && lt(version, low.getVersion())) + return false; + } else { + if (hop == Operator.LT || hop == Operator.LTE) + return false; + if ((lop == Operator.EQ || lop == Operator.LT) && gte(version, low.getVersion())) + return false; + else if (lop == Operator.LTE && gt(version, low.getVersion())) + return false; + } + } + + return true; + } + + /** + * greater than for {@code gtr}, less than for {@code ltr}. + */ + private static boolean edgeGreater(Version a, Version b, boolean above) { + return above ? gt(a, b) : lt(a, b); + } + + /** + * less than for {@code gtr}, greater than for {@code ltr}. + */ + private static boolean edgeLess(Version a, Version b, boolean above) { + return above ? lt(a, b) : gt(a, b); + } + + private static boolean gt(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) > 0; + } + + private static boolean lt(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) < 0; + } + + private static boolean gte(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) >= 0; + } + + private static boolean lte(Version a, Version b) { + return VersionComparison.comparePrecedence(a, b) <= 0; + } + + /** + * Checks whether every version {@code sub} could possibly match is also matched by {@code dom}, e.g. whether {@code sub} is a subset of {@code dom}. + *

+ * {@code *} is the universal set, so anything is a subset of it, and it is a subset of nothing + * else (unless the other side is also {@code *}). Otherwise both ranges are reduced to their + * non empty {@link Interval}s (one per comparator set, dropping contradictory AND-groups). A + * range that reduces to no intervals at all matches nothing, and the empty set is trivially a + * subset of anything. Otherwise {@code sub} is a subset of {@code dom} iff every one of + * {@code sub}'s intervals is wholly contained within a single one of {@code dom}'s intervals + * a sub interval that straddles two dom intervals (with a gap of unmatched versions between them) is not contained in either. + * + * @param sub the candidate subset range + * @param dom the candidate superset (dominating) range + * @return true if every version {@code sub} could match, {@code dom} could also match + */ + public static boolean isSubsetOf(@NotNull Range sub, @NotNull Range dom) { + if (dom.isAny()) + return true; + + if (sub.isAny()) + return false; + + for (final List subSet : sub.getComparatorSets()) { + final Interval subInterval = Interval.of(subSet); + if (subInterval.isEmpty()) + continue; + + boolean contained = false; + for (final List domSet : dom.getComparatorSets()) { + final Interval domInterval = Interval.of(domSet); + if (domInterval.isEmpty()) + continue; + + if (contains(domInterval, subInterval) && prereleaseCovered(subInterval, domSet)) { + contained = true; + break; + } + } + + if (!contained) + return false; + } + + return true; + } + + /** + * If the sub range's lower or upper bound + * carries a prerelease, the matched dominator set must contain a comparator that also has a + * prerelease and shares that bound's major.minor.patch tuple, otherwise the sub range + * admits prereleases in that tuple the dominator does not. The exclusive {@code domSet) { + final Version lower = subInterval.getLower(); + if (lower != null && lower.hasPreRelease() && !domHasPrereleaseAtTuple(domSet, lower)) + return false; + + final Version upper = subInterval.getUpper(); + return upper == null || !upper.hasPreRelease() || isExclusiveZeroPrerelease(subInterval) || domHasPrereleaseAtTuple(domSet, upper); + } + + /** + * Whether the interval's upper bound is an exclusive {@code domSet, @NotNull Version tuple) { + for (final Constraint c : domSet) { + final Version cv = c.getVersion(); + if (cv != null && cv.hasPreRelease() + && cv.getMajor() == tuple.getMajor() + && cv.getMinor() == tuple.getMinor() + && cv.getPatch() == tuple.getPatch()) + return true; + } + + return false; + } + + /** + * Checks whether a (contiguous) interval {@code subI} is wholly contained within another + * (contiguous) interval {@code domI}, e.g. {@code domI}'s lower bound is at or before + * {@code subI}'s lower bound, and {@code domI}'s upper bound is at or after {@code subI}'s + * upper bound, both honoring inclusivity at the boundary. + * + * @param domI the (candidate) containing interval + * @param subI the (candidate) contained interval + * @return true if every version {@code subI} admits is also admitted by {@code domI} + */ + private static boolean contains(@NotNull Interval domI, @NotNull Interval subI) { + final boolean lowerCovered; + if (domI.getLower() == null) { + lowerCovered = true; + } else if (subI.getLower() == null) { + lowerCovered = false; + } else { + final int c = VersionComparison.comparePrecedence(domI.getLower(), subI.getLower()); + if (c < 0) + lowerCovered = true; + else if (c > 0) + lowerCovered = false; + else + lowerCovered = domI.isLowerInclusive() || !subI.isLowerInclusive(); + } + + final boolean upperCovered; + if (domI.getUpper() == null) { + upperCovered = true; + } else if (subI.getUpper() == null) { + upperCovered = false; + } else { + final int c = VersionComparison.comparePrecedence(subI.getUpper(), domI.getUpper()); + if (c < 0) + upperCovered = true; + else if (c > 0) + upperCovered = false; + else + upperCovered = domI.isUpperInclusive() || !subI.isUpperInclusive(); + } + + return lowerCovered && upperCovered; + } + + /** + * Reduces a range's comparator sets to the list of their non empty {@link Interval}s. + * + * @param range the range to inspect + * @return the non empty intervals of {@code range}, one per comparator set that isn't contradictory + */ + private static @NotNull List nonEmptyIntervals(@NotNull Range range) { + final List intervals = new ArrayList<>(); + + for (final List comparatorSet : range.getComparatorSets()) { + final Interval interval = Interval.of(comparatorSet); + if (!interval.isEmpty()) + intervals.add(interval); + } + + return intervals; + } + + /** + * Finds the shortest range that has the same membership as {@code range} over a supplied set of versions. + *

+ * The versions are sorted ascending and walked in order, grouping them into maximal + * contiguous runs of versions that satisfy {@code range}, a run breaks the moment a + * version fails {@link Range#contains(Version)}. Each run of satisfying versions is then + * collapsed to its tightest primitive form: a single satisfying version becomes that bare + * version, and a run spanning several becomes {@code >=first <=last}. The resulting forms are + * OR-ed together. This does not attempt to find the globally shortest string it only + * guarantees membership over the versions supplied is preserved. + * + * @param versions the versions whose membership in {@code range} must be preserved + * @param range the range to simplify + * @return a range equivalent to {@code range} for every version in {@code versions}, if none of the supplied versions satisfy {@code range}, a range that matches nothing + */ + public static @NotNull Range simplify(@NotNull Collection versions, @NotNull Range range) { + final List sorted = new ArrayList<>(versions); + Collections.sort(sorted); + + final List runs = new ArrayList<>(); + Version runFirst = null; + Version runLast = null; + + for (final Version v : sorted) { + if (range.contains(v)) { + if (runFirst == null) + runFirst = v; + + runLast = v; + } else if (runFirst != null) { + runs.add(formatRun(runFirst, runLast)); + runFirst = null; + runLast = null; + } + } + + if (runFirst != null) + runs.add(formatRun(runFirst, runLast)); + + if (runs.isEmpty()) + return Range.parse("<0.0.0-0"); + + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < runs.size(); i++) { + if (i > 0) + sb.append(" || "); + + sb.append(runs.get(i)); + } + + return Range.parse(sb.toString()); + } + + /** + * Formats a single contiguous run of satisfying versions as its tightest primitive range form. + * + * @param first the first (lowest) version of the run + * @param last the last (highest) version of the run + * @return {@code first.toString()} if the run is a single version, otherwise {@code >=first <=last} + */ + private static @NotNull String formatRun(@NotNull Version first, @NotNull Version last) { + if (first.equals(last)) + return first.toString(); + + return ">=" + first + " <=" + last; + } + + /** + * Computes the lowest version an individual (non empty) interval could admit, ignoring + * anything outside of the interval itself (e.g. not yet checked against the owning range's + * other comparator sets or prerelease rule). + * + * @param interval the interval to inspect, must not be empty + * @return the interval's own lower bound candidate + */ + private static @NotNull Version intervalMinimum(@NotNull Interval interval) { + final Version lower = interval.getLower(); + + if (lower == null) + return Version.of(0, 0, 0); + + if (interval.isLowerInclusive()) + return lower; + + if (lower.hasPreRelease()) + return Version.of(lower.getMajor(), lower.getMinor(), lower.getPatch(), lower.getPreRelease() + ".0"); + + return Version.of(lower.getMajor(), lower.getMinor(), lower.getPatch() + 1); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java new file mode 100644 index 0000000..c5243f3 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/RangeParsing.java @@ -0,0 +1,379 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.Operator; +import io.github.milkdrinkers.javasemver.exception.RangeParseException; +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Range parsing logic used by {@code Range}. + * + * @apiNote Handles the OR-split ({@code ||}), hyphen ranges (e.g. {@code 1.2.3 - 2.3.4}), + * space separated primitive comparators (plus the {@code *}/empty wildcard), X-ranges/partial + * versions (e.g. {@code 1.x}, {@code 1.2.*}, {@code 1}, {@code 1.2}), and tilde/caret ranges (e.g. {@code ~1.2.3}, {@code ^1.2.3}) within each OR-part. + */ +@ApiStatus.Internal +public final class RangeParsing { + /** + * Matches a bare (no leading operator) X-range or partial version token e.g. {@code 1}, + * {@code 1.2}, {@code 1.2.3}, {@code 1.x}, {@code 1.2.*}. Each of the up to three dot separated + * components is either a non negative integer or a wildcard marker ({@code x}, {@code X} or {@code *}), trailing components may be omitted entirely. + */ + private static final Pattern X_RANGE_PATTERN = Pattern.compile("^([0-9]+|[xX*])(\\.([0-9]+|[xX*])(\\.([0-9]+|[xX*]))?)?$"); + + /** + * Matches an entire hyphen range OR-part e.g. {@code 1.2.3 - 2.3.4} a token, then a hyphen + * surrounded by whitespace, then a token. Must be checked before the OR-part is split on + * whitespace, since the from/to tokens themselves contain no spaces but are separated by them. + */ + private static final Pattern HYPHEN_RANGE_PATTERN = Pattern.compile("^\\s*(\\S+)\\s+-\\s+(\\S+)\\s*$"); + + private RangeParsing() { + } + + /** + * Parses a range string into a list of comparator sets, where the outer list represents an OR relationship between sets and the inner list represents an AND relationship between the constraints of a set. + * + * @param rangeStr the range string to parse + * @return the parsed comparator sets + * @throws RangeParseException thrown if the range string could not be parsed + */ + public static List> parse(String rangeStr) throws RangeParseException { + return parse(rangeStr, false); + } + + /** + * Parses a range string into a list of comparator sets, where the outer list represents an + * OR relationship between sets and the inner list represents an AND relationship between the constraints of a set. + * + * @param rangeStr the range string to parse + * @param loose whether primitive comparator versions (e.g. {@code >=v1.2.0}) should be parsed leniently via {@link io.github.milkdrinkers.javasemver.Version#parseLoose(String)} rather than strictly + * @return the parsed comparator sets + * @throws RangeParseException thrown if the range string could not be parsed + */ + public static List> parse(String rangeStr, boolean loose) throws RangeParseException { + if (rangeStr == null) + throw new RangeParseException("Could not parse range from: null"); + + final String trimmed = rangeStr.trim(); + + if (trimmed.isEmpty() || trimmed.equals("*")) { + final List anySet = new ArrayList<>(); + anySet.add(Constraint.any()); + final List> comparatorSets = new ArrayList<>(); + comparatorSets.add(anySet); + return comparatorSets; + } + + final List> comparatorSets = new ArrayList<>(); + for (String orPart : trimmed.split("\\|\\|")) { + if (orPart.trim().isEmpty()) + continue; + + final List comparatorSet = parseComparatorSet(orPart, loose); + if (!comparatorSet.isEmpty()) + comparatorSets.add(comparatorSet); + } + + if (comparatorSets.isEmpty()) + throw new RangeParseException("Could not parse range from: " + rangeStr); + + return comparatorSets; + } + + /** + * Parses a single OR-part (a space separated series of comparators that must all match, e.g. an AND relationship) into a comparator set. + * + *

A hyphen range (e.g. {@code 1.2.3 - 2.3.4}) is detected first, before the OR-part is + * split on whitespace, since it is itself whitespace separated (unlike every other supported + * token). Anything that is not a hyphen range falls through to the original token by token handling.

+ */ + private static List parseComparatorSet(String part, boolean loose) { + final Matcher hyphenMatcher = HYPHEN_RANGE_PATTERN.matcher(part); + if (hyphenMatcher.matches()) + return expandHyphenRange(part, hyphenMatcher.group(1), hyphenMatcher.group(2)); + + final List constraints = new ArrayList<>(); + + for (String token : part.trim().split("\\s+")) { + if (token.isEmpty()) + continue; + + if (token.charAt(0) == '~') { + constraints.addAll(expandTildeRange(token)); + continue; + } + + if (token.charAt(0) == '^') { + constraints.addAll(expandCaretRange(token)); + continue; + } + + if (X_RANGE_PATTERN.matcher(token).matches()) { + constraints.addAll(expandXRange(token)); + continue; + } + + constraints.add(Constraint.parse(token, loose)); + } + + return constraints; + } + + /** + * Expands a hyphen range (e.g. {@code 1.2.3 - 2.3.4}), already split into its {@code from} and {@code to} tokens by {@link #HYPHEN_RANGE_PATTERN}, into the equivalent {@code >=lower <=upper} (or {@code + *
  • {@code from} is partial → missing components are filled with zero for the lower + * bound e.g. {@code 1.2 - ...} → {@code >=1.2.0 ...}, {@code 1 - ...} → + * {@code >=1.0.0 ...}.
  • + *
  • {@code to} is fully specified → {@code <=} that version (prerelease preserved).
  • + *
  • {@code to} is partial → the next component up is bumped and the bound becomes + * exclusive e.g. {@code ... - 2.3} → {@code ... <2.4.0-0}, {@code ... - 2} → + * {@code ... <3.0.0-0}.
  • + * + */ + private static List expandHyphenRange(String part, String fromToken, String toToken) { + final PartialVersion from = parsePartial(part, fromToken); + final PartialVersion to = parsePartial(part, toToken); + + final List constraints = new ArrayList<>(); + + if (!from.any) + constraints.add(new Constraint(Operator.GTE, from.toLowerVersion())); + + if (to.any) + return constraints; + + if (to.specified >= 3) { + constraints.add(new Constraint(Operator.LTE, to.toLowerVersion())); + return constraints; + } + + final Version upper = to.specified == 2 + ? Version.of(to.major, to.minor + 1L, 0L, "0") + : Version.of(to.major + 1L, 0L, 0L, "0"); + constraints.add(new Constraint(Operator.LT, upper)); + + return constraints; + } + + /** + * Expands a bare X-range/partial version token (already validated against {@link #X_RANGE_PATTERN}) into the equivalent list of AND-ed constraints. + * + *
      + *
    • major is a wildcard (e.g. {@code x}, {@code *}) → a single {@link Constraint#any()}
    • + *
    • minor is a wildcard or absent (e.g. {@code 1}, {@code 1.x}) → {@code >=M.0.0 <(M+1).0.0}
    • + *
    • patch is a wildcard or absent (e.g. {@code 1.2}, {@code 1.2.x}) → {@code >=M.m.0 + *
    • fully specified (e.g. {@code 1.2.3}) → a single {@code =M.m.p} equality
    • + *
    + */ + private static List expandXRange(String token) { + final String[] parts = token.split("\\.", -1); + final String majorPart = parts[0]; + final String minorPart = parts.length > 1 ? parts[1] : null; + final String patchPart = parts.length > 2 ? parts[2] : null; + + final List constraints = new ArrayList<>(); + + if (isWildcardComponent(majorPart)) { + constraints.add(Constraint.any()); + return constraints; + } + + try { + final long major = Long.parseLong(majorPart); + + if (isWildcardComponent(minorPart)) { + constraints.add(new Constraint(Operator.GTE, Version.of(major, 0L, 0L))); + constraints.add(new Constraint(Operator.LT, Version.of(major + 1L, 0L, 0L, "0"))); + return constraints; + } + + final long minor = Long.parseLong(minorPart); + + if (isWildcardComponent(patchPart)) { + constraints.add(new Constraint(Operator.GTE, Version.of(major, minor, 0L))); + constraints.add(new Constraint(Operator.LT, Version.of(major, minor + 1L, 0L, "0"))); + return constraints; + } + + final long patch = Long.parseLong(patchPart); + constraints.add(new Constraint(Operator.EQ, Version.of(major, minor, patch))); + return constraints; + } catch (NumberFormatException e) { + throw new RangeParseException("Could not parse range from: " + token, e); + } + } + + /** + * Expands a tilde range token (e.g. {@code ~1.2.3}), which already carries its leading {@code ~}, into the equivalent {@code >=lower Allows patch level changes if minor is specified, otherwise minor level changes:

    + *
      + *
    • {@code ~M.m.p} → {@code >=M.m.p + *
    • {@code ~M.m} → {@code >=M.m.0 + *
    • {@code ~M} → {@code >=M.0.0 <(M+1).0.0-0}
    • + *
    + * + *

    A prerelease on the version part (e.g. {@code ~1.2.3-beta}) is preserved on the lower bound but never affects the upper bound.

    + */ + private static List expandTildeRange(String token) { + final PartialVersion partial = parsePartial(token, token.substring(1)); + + final List constraints = new ArrayList<>(); + if (partial.any) { + constraints.add(Constraint.any()); + return constraints; + } + + constraints.add(new Constraint(Operator.GTE, partial.toLowerVersion())); + + final Version upper = partial.specified >= 2 + ? Version.of(partial.major, partial.minor + 1L, 0L, "0") + : Version.of(partial.major + 1L, 0L, 0L, "0"); + constraints.add(new Constraint(Operator.LT, upper)); + + return constraints; + } + + /** + * Expands a caret range token (e.g. {@code ^1.2.3}), which already carries its leading {@code ^}, into the equivalent {@code >=lower Allows changes that do not modify the left most non zero of the specified + * components e.g. {@code ^1.2.3} → {@code >=1.2.3 <2.0.0-0}, {@code ^0.2.3} → + * {@code >=0.2.3 <0.3.0-0}, {@code ^0.0.3} → {@code >=0.0.3 <0.0.4-0}. If every specified + * component is zero (e.g. {@code ^0.0}), the least significant specified component is bumped + * instead (e.g. {@code ^0.0} → {@code <0.1.0-0}, {@code ^0} → {@code <1.0.0-0}).

    + * + *

    A prerelease on the version part is preserved on the lower bound but never affects the upper bound.

    + */ + private static List expandCaretRange(String token) { + final PartialVersion partial = parsePartial(token, token.substring(1)); + + final List constraints = new ArrayList<>(); + if (partial.any) { + constraints.add(Constraint.any()); + return constraints; + } + + constraints.add(new Constraint(Operator.GTE, partial.toLowerVersion())); + constraints.add(new Constraint(Operator.LT, caretUpperBound(partial))); + + return constraints; + } + + /** + * Computes the caret upper bound: the left most non zero of the specified major/minor/patch + * components is incremented and everything to its right is zeroed. If every specified + * component is zero, the least significant specified component is incremented instead. + */ + private static Version caretUpperBound(PartialVersion partial) { + if (partial.major > 0L) + return Version.of(partial.major + 1L, 0L, 0L, "0"); + + if (partial.specified >= 2 && partial.minor > 0L) + return Version.of(0L, partial.minor + 1L, 0L, "0"); + + if (partial.specified >= 3 && partial.patch > 0L) + return Version.of(0L, 0L, partial.patch + 1L, "0"); + + switch (partial.specified) { + case 1: + return Version.of(1L, 0L, 0L, "0"); + case 2: + return Version.of(0L, 1L, 0L, "0"); + default: + return Version.of(0L, 0L, 1L, "0"); + } + } + + /** + * Parses the version part following a stripped {@code ~}/{@code ^} prefix into a + * {@link PartialVersion}, tolerating X-range wildcards/absent trailing components (e.g. + * {@code 1}, {@code 1.2}, {@code 1.x}) and a full version carrying a prerelease (e.g. {@code 1.2.3-beta}). + * + * @param token the original token (including its {@code ~}/{@code ^} prefix), used only for error messages + * @param remainder the version part with the {@code ~}/{@code ^} prefix already stripped + */ + private static PartialVersion parsePartial(String token, String remainder) { + try { + // build metadata never affects, strip here + final int plusIndex = remainder.indexOf('+'); + final String noBuild = plusIndex >= 0 ? remainder.substring(0, plusIndex) : remainder; + + if (noBuild.contains("-")) { + final Version v = Version.parse(noBuild); + return new PartialVersion(v.getMajor(), v.getMinor(), v.getPatch(), 3, v.getPreRelease(), false); + } + + if (!X_RANGE_PATTERN.matcher(noBuild).matches()) + throw new RangeParseException("Could not parse range from: " + token); + + final String[] parts = noBuild.split("\\.", -1); + final String majorPart = parts[0]; + final String minorPart = parts.length > 1 ? parts[1] : null; + final String patchPart = parts.length > 2 ? parts[2] : null; + + if (isWildcardComponent(majorPart)) + return new PartialVersion(0L, 0L, 0L, 0, "", true); + + final long major = Long.parseLong(majorPart); + + if (isWildcardComponent(minorPart)) + return new PartialVersion(major, 0L, 0L, 1, "", false); + + final long minor = Long.parseLong(minorPart); + + if (isWildcardComponent(patchPart)) + return new PartialVersion(major, minor, 0L, 2, "", false); + + final long patch = Long.parseLong(patchPart); + return new PartialVersion(major, minor, patch, 3, "", false); + } catch (RangeParseException e) { + throw e; + } catch (RuntimeException e) { + throw new RangeParseException("Could not parse range from: " + token, e); + } + } + + /** + * Checks whether an X-range component is a wildcard, e.g. {@code x}, {@code X}, {@code *}, or absent (null). + */ + private static boolean isWildcardComponent(String component) { + return component == null || component.equals("x") || component.equals("X") || component.equals("*"); + } + + /** + * The decomposed major/minor/patch/prerelease of a tilde/caret version part, along with how + * many of the leading components were actually specified (as opposed to wildcard/absent) and + * whether the whole thing was a bare wildcard (e.g. {@code ~x}, {@code ^*}). + */ + private static final class PartialVersion { + private final long major; + private final long minor; + private final long patch; + private final int specified; + private final String preRelease; + private final boolean any; + + private PartialVersion(long major, long minor, long patch, int specified, String preRelease, boolean any) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.specified = specified; + this.preRelease = preRelease; + this.any = any; + } + + private Version toLowerVersion() { + return preRelease.isEmpty() ? Version.of(major, minor, patch) : Version.of(major, minor, patch, preRelease); + } + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java new file mode 100644 index 0000000..4b48f8d --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionComparison.java @@ -0,0 +1,142 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Version; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Precedence and build aware comparison logic used by {@link Version}. + */ +@ApiStatus.Internal +public final class VersionComparison { + private VersionComparison() { + } + + /** + * Compares two versions by Semantic Versioning precedence: major, minor, patch and then + * pre release identifiers. Build metadata plays no role in this comparison. + * + * @param a the first version + * @param b the second version + * @return {@code -1}, {@code 0} or {@code 1} as {@code a} is less than, equal to, or greater than {@code b} + */ + public static int comparePrecedence(@NotNull Version a, @NotNull Version b) { + int result = signum(a.getMajor() - b.getMajor()); + if (result != 0) + return result; + + result = signum(a.getMinor() - b.getMinor()); + if (result != 0) + return result; + + result = signum(a.getPatch() - b.getPatch()); + if (result != 0) + return result; + + return comparePreRelease(a.getPreReleaseIdentifiers(), b.getPreReleaseIdentifiers()); + } + + /** + * Compares two versions by Semantic Versioning precedence and then by build metadata as a tiebreak. + * A version with build metadata is considered greater than an otherwise equal version without any. This is the opposite of how missing pre release identifiers are treated. + * + * @param a the first version + * @param b the second version + * @return {@code -1}, {@code 0} or {@code 1} as {@code a} is less than, equal to, or greater than {@code b} + */ + public static int compareBuild(@NotNull Version a, @NotNull Version b) { + final int p = comparePrecedence(a, b); + if (p != 0) + return p; + + return compareBuildIdentifiers(splitDot(a.getBuildMetadata()), splitDot(b.getBuildMetadata())); + } + + /** + * Compares pre release identifier lists. + */ + private static int comparePreRelease(@NotNull List currentIdentifiers, @NotNull List otherIdentifiers) { + if (currentIdentifiers.isEmpty()) { + return otherIdentifiers.isEmpty() ? 0 : 1; + } else if (otherIdentifiers.isEmpty()) { + return -1; + } + + final int minLength = Math.min(currentIdentifiers.size(), otherIdentifiers.size()); + + for (int i = 0; i < minLength; i++) { + final int comparison = compareIdentifier(currentIdentifiers.get(i), otherIdentifiers.get(i)); + if (comparison != 0) + return comparison; + } + + return signum(currentIdentifiers.size() - otherIdentifiers.size()); + } + + /** + * Compares build metadata identifier lists. Unlike pre release identifiers, an empty (missing) build metadata identifier list sorts lower, not higher. + */ + private static int compareBuildIdentifiers(@NotNull List currentIdentifiers, @NotNull List otherIdentifiers) { + final int minLength = Math.min(currentIdentifiers.size(), otherIdentifiers.size()); + + for (int i = 0; i < minLength; i++) { + final int comparison = compareIdentifier(currentIdentifiers.get(i), otherIdentifiers.get(i)); + if (comparison != 0) + return comparison; + } + + return signum(currentIdentifiers.size() - otherIdentifiers.size()); + } + + /** + * Compares a single pair of dot separated identifiers using the numeric vs alphanumeric rule. + * Numeric identifiers are compared numerically and are always lower than alphanumeric ones, which are compared lexically. + */ + private static int compareIdentifier(@NotNull String currentId, @NotNull String otherId) { + final boolean isCurrentNumeric = isNumeric(currentId); + final boolean isOtherNumeric = isNumeric(otherId); + + if (isCurrentNumeric && !isOtherNumeric) { + return -1; + } else if (!isCurrentNumeric && isOtherNumeric) { + return 1; + } else if (isCurrentNumeric) { + return signum(Long.parseLong(currentId) - Long.parseLong(otherId)); + } else { + return signum(currentId.compareTo(otherId)); + } + } + + /** + * Checks if an identifier is numeric, e.g. consists only of digits. Leading zeros are allowed. Strictly parsed pre release identifiers can never carry them, but build metadata identifiers may. + * + * @param identifier The identifier to check + * @return true if the identifier is numeric, false otherwise + */ + private static boolean isNumeric(@Nullable String identifier) { + return identifier != null && !identifier.isEmpty() && identifier.matches("\\d+"); + } + + /** + * Splits a dot separated identifier string into its component identifiers, treating an empty string as an empty list rather than a single empty identifier. + */ + private static @NotNull List splitDot(@NotNull String value) { + final List ids = new ArrayList<>(); + for (String s : value.split("\\.")) { + if (!s.isEmpty()) + ids.add(s); + } + return ids; + } + + /** + * Clamps an arbitrary integer difference to {@code -1}, {@code 0} or {@code 1}. + */ + private static int signum(long value) { + return Long.compare(value, 0L); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java new file mode 100644 index 0000000..acc7082 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/VersionParsing.java @@ -0,0 +1,86 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.exception.VersionParseException; +import org.jetbrains.annotations.ApiStatus; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Strict semantic version parsing logic used by {@link Version}. + */ +@ApiStatus.Internal +public final class VersionParsing { + private static final Pattern SEMVER_REGEX = Pattern.compile( + "^(?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-]+)*))?$"); + + private static final Pattern LOOSE_SEMVER_REGEX = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z-.]+))?(?:\\+([0-9A-Za-z-.]+))?$"); + + private VersionParsing() { + } + + public static Version parseStrict(String input) { + if (input == null) + throw new VersionParseException("Version string was null."); + + String s = input.trim(); + + if (s.length() >= 1 && (s.charAt(0) == 'v' || s.charAt(0) == 'V')) + s = s.substring(1); + + final Matcher m = SEMVER_REGEX.matcher(s); + if (!m.matches()) + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input)); + + final String pre = m.group("prerelease"); + final String meta = m.group("meta"); + + try { + return Version.of( + Long.parseLong(m.group("major")), + Long.parseLong(m.group("minor")), + Long.parseLong(m.group("patch")), + pre == null ? "" : pre, + meta == null ? "" : meta + ); + } catch (NumberFormatException e) { + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input), e); + } + } + + public static Version parseLoose(String input) { + if (input == null) + throw new VersionParseException("Version string was null."); + + String s = input.trim(); + + if (s.length() >= 1 && s.charAt(0) == '=') + s = s.substring(1); + + if (s.length() >= 1 && (s.charAt(0) == 'v' || s.charAt(0) == 'V')) + s = s.substring(1); + + final Matcher m = LOOSE_SEMVER_REGEX.matcher(s); + if (!m.matches()) + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input)); + + final String pre = m.group(4); + final String meta = m.group(5); + + try { + return Version.of( + Long.parseLong(m.group(1)), + Long.parseLong(m.group(2)), + Long.parseLong(m.group(3)), + pre == null ? "" : pre, + meta == null ? "" : meta + ); + } catch (NumberFormatException e) { + throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", input), e); + } + } +} diff --git a/common/src/test/java/SemVerTest.java b/common/src/test/java/SemVerTest.java index 14a1f26..26c95be 100644 --- a/common/src/test/java/SemVerTest.java +++ b/common/src/test/java/SemVerTest.java @@ -16,7 +16,7 @@ public class SemVerTest { class ParsingTests { @Test public void testBasicParsing() { - Version v = Version.of("1.2.3"); + Version v = Version.parse("1.2.3"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -26,7 +26,7 @@ public void testBasicParsing() { @Test public void testParsingWithPreRelease() { - Version v = Version.of("1.2.3-alpha.1"); + Version v = Version.parse("1.2.3-alpha.1"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -36,7 +36,7 @@ public void testParsingWithPreRelease() { @Test public void testParsingWithBuildMetadata() { - Version v = Version.of("1.2.3+build.20230101"); + Version v = Version.parse("1.2.3+build.20230101"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -46,7 +46,7 @@ public void testParsingWithBuildMetadata() { @Test public void testParsingWithPreReleaseAndBuildMetadata() { - Version v = Version.of("1.2.3-alpha.1+build.20230101"); + Version v = Version.parse("1.2.3-alpha.1+build.20230101"); Assertions.assertEquals(1, v.getMajor()); Assertions.assertEquals(2, v.getMinor()); Assertions.assertEquals(3, v.getPatch()); @@ -56,7 +56,7 @@ public void testParsingWithPreReleaseAndBuildMetadata() { @Test public void testParsingWithLeadingZeros() { - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.02.03")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.02.03")); } @ParameterizedTest @@ -73,7 +73,7 @@ public void testParsingWithLeadingZeros() { "-1.2.3" }) public void testInvalidVersions(String version) { - Assertions.assertThrows(VersionParseException.class, () -> Version.of(version)); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse(version)); } @ParameterizedTest @@ -86,7 +86,7 @@ public void testInvalidVersions(String version) { "1.2.3-rc.1+build.123" }) public void testValidVersions(String version) { - Assertions.assertDoesNotThrow(() -> Version.of(version)); + Assertions.assertDoesNotThrow(() -> Version.parse(version)); } } @@ -94,81 +94,81 @@ public void testValidVersions(String version) { class ComparisonTests { @Test public void testReleaseVsPreRelease() { - Version release = Version.of("1.0.0"); - Version preRelease = Version.of("1.0.0-alpha"); + Version release = Version.parse("1.0.0"); + Version preRelease = Version.parse("1.0.0-alpha"); - Assertions.assertTrue(Version.isNewer(release, preRelease)); - Assertions.assertTrue(Version.isNewerOrEqual(release, preRelease)); - Assertions.assertFalse(Version.isEqual(release, preRelease)); - Assertions.assertFalse(Version.isOlderOrEqual(release, preRelease)); - Assertions.assertFalse(Version.isOlder(release, preRelease)); + Assertions.assertTrue(release.isGreaterThan(preRelease)); + Assertions.assertTrue(release.isAtLeast(preRelease)); + Assertions.assertFalse(release.isEqualTo(preRelease)); + Assertions.assertFalse(release.isAtMost(preRelease)); + Assertions.assertFalse(release.isLessThan(preRelease)); } @Test public void testPreReleaseComparisons() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-alpha.1"); - Version v3 = Version.of("1.0.0-alpha.beta"); - Version v4 = Version.of("1.0.0-beta"); - Version v5 = Version.of("1.0.0-beta.2"); - Version v6 = Version.of("1.0.0-beta.11"); - Version v7 = Version.of("1.0.0-rc.1"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-alpha.1"); + Version v3 = Version.parse("1.0.0-alpha.beta"); + Version v4 = Version.parse("1.0.0-beta"); + Version v5 = Version.parse("1.0.0-beta.2"); + Version v6 = Version.parse("1.0.0-beta.11"); + Version v7 = Version.parse("1.0.0-rc.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); - Assertions.assertTrue(Version.isOlder(v2, v3)); - Assertions.assertTrue(Version.isOlder(v3, v4)); - Assertions.assertTrue(Version.isOlder(v4, v5)); - Assertions.assertTrue(Version.isOlder(v5, v6)); - Assertions.assertTrue(Version.isOlder(v6, v7)); + Assertions.assertTrue(v1.isLessThan(v2)); + Assertions.assertTrue(v2.isLessThan(v3)); + Assertions.assertTrue(v3.isLessThan(v4)); + Assertions.assertTrue(v4.isLessThan(v5)); + Assertions.assertTrue(v5.isLessThan(v6)); + Assertions.assertTrue(v6.isLessThan(v7)); } @Test public void testBuildMetadataIgnored() { - Version v1 = Version.of("1.0.0+build.1"); - Version v2 = Version.of("1.0.0+build.2"); + Version v1 = Version.parse("1.0.0+build.1"); + Version v2 = Version.parse("1.0.0+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); - Assertions.assertTrue(Version.isNewerOrEqual(v1, v2)); - Assertions.assertTrue(Version.isOlderOrEqual(v1, v2)); - Assertions.assertFalse(Version.isNewer(v1, v2)); - Assertions.assertFalse(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); + Assertions.assertTrue(v1.isAtLeast(v2)); + Assertions.assertTrue(v1.isAtMost(v2)); + Assertions.assertFalse(v1.isGreaterThan(v2)); + Assertions.assertFalse(v1.isLessThan(v2)); } @Test public void testPreReleaseWithBuildMetadata() { - Version v1 = Version.of("1.0.0-alpha+build.1"); - Version v2 = Version.of("1.0.0-alpha+build.2"); + Version v1 = Version.parse("1.0.0-alpha+build.1"); + Version v2 = Version.parse("1.0.0-alpha+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); - Version v3 = Version.of("1.0.0-alpha.1+build.1"); - Assertions.assertTrue(Version.isNewer(v3, v1)); + Version v3 = Version.parse("1.0.0-alpha.1+build.1"); + Assertions.assertTrue(v3.isGreaterThan(v1)); } @Test public void testNumericVsAlphabeticPreRelease() { - Version v1 = Version.of("1.0.0-alpha.1"); - Version v2 = Version.of("1.0.0-alpha.beta"); + Version v1 = Version.parse("1.0.0-alpha.1"); + Version v2 = Version.parse("1.0.0-alpha.beta"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-beta.2"); - Version v4 = Version.of("1.0.0-beta.11"); + Version v3 = Version.parse("1.0.0-beta.2"); + Version v4 = Version.parse("1.0.0-beta.11"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } @ParameterizedTest @MethodSource("provideVersionPairs") public void testVersionOrder(String olderVersion, String newerVersion) { - Version older = Version.of(olderVersion); - Version newer = Version.of(newerVersion); + Version older = Version.parse(olderVersion); + Version newer = Version.parse(newerVersion); - Assertions.assertTrue(Version.isOlder(older, newer), + Assertions.assertTrue(older.isLessThan(newer), olderVersion + " should be older than " + newerVersion); - Assertions.assertTrue(Version.isNewer(newer, older), + Assertions.assertTrue(newer.isGreaterThan(older), newerVersion + " should be newer than " + olderVersion); - Assertions.assertFalse(Version.isEqual(older, newer)); + Assertions.assertFalse(older.isEqualTo(newer)); } static Stream provideVersionPairs() { @@ -195,38 +195,38 @@ static Stream provideVersionPairs() { class EquivalenceTests { @Test public void testIdenticalVersionsEqual() { - Version v1 = Version.of("1.2.3"); - Version v2 = Version.of("1.2.3"); + Version v1 = Version.parse("1.2.3"); + Version v2 = Version.parse("1.2.3"); - Assertions.assertTrue(Version.isEqual(v1, v2)); - Assertions.assertTrue(Version.isNewerOrEqual(v1, v2)); - Assertions.assertTrue(Version.isOlderOrEqual(v1, v2)); - Assertions.assertFalse(Version.isNewer(v1, v2)); - Assertions.assertFalse(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); + Assertions.assertTrue(v1.isAtLeast(v2)); + Assertions.assertTrue(v1.isAtMost(v2)); + Assertions.assertFalse(v1.isGreaterThan(v2)); + Assertions.assertFalse(v1.isLessThan(v2)); } @Test public void testVersionsWithDifferentBuildMetadataEqual() { - Version v1 = Version.of("1.2.3+build.1"); - Version v2 = Version.of("1.2.3+build.2"); + Version v1 = Version.parse("1.2.3+build.1"); + Version v2 = Version.parse("1.2.3+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); } @Test public void testVersionsWithSamePreReleaseEqual() { - Version v1 = Version.of("1.2.3-alpha"); - Version v2 = Version.of("1.2.3-alpha"); + Version v1 = Version.parse("1.2.3-alpha"); + Version v2 = Version.parse("1.2.3-alpha"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); } @Test public void testVersionsWithSamePreReleaseAndDifferentBuildMetadataEqual() { - Version v1 = Version.of("1.2.3-alpha+build.1"); - Version v2 = Version.of("1.2.3-alpha+build.2"); + Version v1 = Version.parse("1.2.3-alpha+build.1"); + Version v2 = Version.parse("1.2.3-alpha+build.2"); - Assertions.assertTrue(Version.isEqual(v1, v2)); + Assertions.assertTrue(v1.isEqualTo(v2)); } } @@ -234,52 +234,52 @@ public void testVersionsWithSamePreReleaseAndDifferentBuildMetadataEqual() { class EdgeCaseTests { @Test public void testZeroVersions() { - Version v1 = Version.of("0.0.0"); - Version v2 = Version.of("0.0.1"); + Version v1 = Version.parse("0.0.0"); + Version v2 = Version.parse("0.0.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testVeryLargeNumbers() { - Version v1 = Version.of("999999.999999.999999"); - Version v2 = Version.of("1000000.0.0"); + Version v1 = Version.parse("999999.999999.999999"); + Version v2 = Version.parse("1000000.0.0"); - Assertions.assertTrue(Version.isOlderOrEqual(v1, v2)); + Assertions.assertTrue(v1.isAtMost(v2)); } @Test public void testNumericPreReleaseIdentifiers() { - Version v1 = Version.of("1.0.0-1"); - Version v2 = Version.of("1.0.0-2"); + Version v1 = Version.parse("1.0.0-1"); + Version v2 = Version.parse("1.0.0-2"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testAlphanumericPreReleaseIdentifiers() { - Version v1 = Version.of("1.0.0-a1"); - Version v2 = Version.of("1.0.0-a2"); + Version v1 = Version.parse("1.0.0-a1"); + Version v2 = Version.parse("1.0.0-a2"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testDifferentLengthPreReleaseIdentifiers() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-alpha.1"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-alpha.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); } @Test public void testSpecialPreReleaseIdentifiers() { - Version snapshot = Version.of("2.0.0-snapshot"); - Version beta = Version.of("2.0.0-beta.2"); - Version release = Version.of("2.0.0"); + Version snapshot = Version.parse("2.0.0-snapshot"); + Version beta = Version.parse("2.0.0-beta.2"); + Version release = Version.parse("2.0.0"); - Assertions.assertTrue(Version.isNewer(snapshot, beta)); - Assertions.assertTrue(Version.isNewer(release, snapshot)); + Assertions.assertTrue(snapshot.isGreaterThan(beta)); + Assertions.assertTrue(release.isGreaterThan(snapshot)); } } @@ -287,41 +287,41 @@ public void testSpecialPreReleaseIdentifiers() { class PreReleaseIdentifierOrderingTests { @Test public void testNumericIdentifiersComparedNumerically() { - Version v1 = Version.of("1.0.0-1"); - Version v2 = Version.of("1.0.0-alpha"); + Version v1 = Version.parse("1.0.0-1"); + Version v2 = Version.parse("1.0.0-alpha"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-2"); - Version v4 = Version.of("1.0.0-10"); + Version v3 = Version.parse("1.0.0-2"); + Version v4 = Version.parse("1.0.0-10"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } @Test public void testIdentifiersWithLettersComparedLexically() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-beta"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-beta"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-abc"); - Version v4 = Version.of("1.0.0-abd"); + Version v3 = Version.parse("1.0.0-abc"); + Version v4 = Version.parse("1.0.0-abd"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } @Test public void testIdentifiersWithSamePrefixComparedByLength() { - Version v1 = Version.of("1.0.0-alpha"); - Version v2 = Version.of("1.0.0-alpha.1"); + Version v1 = Version.parse("1.0.0-alpha"); + Version v2 = Version.parse("1.0.0-alpha.1"); - Assertions.assertTrue(Version.isOlder(v1, v2)); + Assertions.assertTrue(v1.isLessThan(v2)); - Version v3 = Version.of("1.0.0-alpha.beta"); - Version v4 = Version.of("1.0.0-alpha.beta.1"); + Version v3 = Version.parse("1.0.0-alpha.beta"); + Version v4 = Version.parse("1.0.0-alpha.beta.1"); - Assertions.assertTrue(Version.isOlder(v3, v4)); + Assertions.assertTrue(v3.isLessThan(v4)); } } @@ -330,18 +330,18 @@ class ComplexComparisonTests { @Test public void testMixedVersionComparisons() { Version[] versions = new Version[]{ - Version.of("1.0.0"), - Version.of("1.0.1"), - Version.of("1.1.0"), - Version.of("1.1.1"), - Version.of("2.0.0"), - Version.of("2.1.0") + Version.parse("1.0.0"), + Version.parse("1.0.1"), + Version.parse("1.1.0"), + Version.parse("1.1.1"), + Version.parse("2.0.0"), + Version.parse("2.1.0") }; for (int i = 0; i < versions.length - 1; i++) { for (int j = i + 1; j < versions.length; j++) { - Assertions.assertTrue(Version.isOlder(versions[i], versions[j])); - Assertions.assertTrue(Version.isNewer(versions[j], versions[i])); + Assertions.assertTrue(versions[i].isLessThan(versions[j])); + Assertions.assertTrue(versions[j].isGreaterThan(versions[i])); } } } @@ -349,31 +349,31 @@ public void testMixedVersionComparisons() { @Test public void testPreReleaseVsReleaseForSameVersion() { Version[] versions = new Version[]{ - Version.of("1.0.0-alpha"), - Version.of("1.0.0-alpha.1"), - Version.of("1.0.0-beta"), - Version.of("1.0.0-beta.2"), - Version.of("1.0.0-beta.11"), - Version.of("1.0.0-rc.1"), - Version.of("1.0.0") + Version.parse("1.0.0-alpha"), + Version.parse("1.0.0-alpha.1"), + Version.parse("1.0.0-beta"), + Version.parse("1.0.0-beta.2"), + Version.parse("1.0.0-beta.11"), + Version.parse("1.0.0-rc.1"), + Version.parse("1.0.0") }; for (int i = 0; i < versions.length - 1; i++) { for (int j = i + 1; j < versions.length; j++) { - Assertions.assertTrue(Version.isOlder(versions[i], versions[j])); - Assertions.assertTrue(Version.isNewer(versions[j], versions[i])); + Assertions.assertTrue(versions[i].isLessThan(versions[j])); + Assertions.assertTrue(versions[j].isGreaterThan(versions[i])); } } } @Test public void testPreReleaseVsHigherVersion() { - Version preRelease = Version.of("2.0.0-alpha"); - Version lowerVersion = Version.of("1.9.9"); - Version higherVersion = Version.of("2.0.1"); + Version preRelease = Version.parse("2.0.0-alpha"); + Version lowerVersion = Version.parse("1.9.9"); + Version higherVersion = Version.parse("2.0.1"); - Assertions.assertTrue(Version.isNewer(preRelease, lowerVersion)); - Assertions.assertTrue(Version.isOlder(preRelease, higherVersion)); + Assertions.assertTrue(preRelease.isGreaterThan(lowerVersion)); + Assertions.assertTrue(preRelease.isLessThan(higherVersion)); } } @@ -381,16 +381,16 @@ public void testPreReleaseVsHigherVersion() { class FormattingTests { @Test public void testToString() { - Version v1 = Version.of("1.2.3"); + Version v1 = Version.parse("1.2.3"); Assertions.assertEquals("1.2.3", v1.toString()); - Version v2 = Version.of("1.2.3-alpha"); + Version v2 = Version.parse("1.2.3-alpha"); Assertions.assertEquals("1.2.3-alpha", v2.toString()); - Version v3 = Version.of("1.2.3+build.1"); + Version v3 = Version.parse("1.2.3+build.1"); Assertions.assertEquals("1.2.3+build.1", v3.toString()); - Version v4 = Version.of("1.2.3-alpha+build.1"); + Version v4 = Version.parse("1.2.3-alpha+build.1"); Assertions.assertEquals("1.2.3-alpha+build.1", v4.toString()); } } @@ -418,28 +418,28 @@ class CommonPreReleasePatternTests { @Test public void testCommonPreReleasePatterns() { Version[] versions = new Version[]{ - Version.of("1.0.0-alpha"), - Version.of("1.0.0-alpha.1"), - Version.of("1.0.0-beta"), - Version.of("1.0.0-beta.1"), - Version.of("1.0.0-rc"), - Version.of("1.0.0-rc.1"), - Version.of("1.0.0") + Version.parse("1.0.0-alpha"), + Version.parse("1.0.0-alpha.1"), + Version.parse("1.0.0-beta"), + Version.parse("1.0.0-beta.1"), + Version.parse("1.0.0-rc"), + Version.parse("1.0.0-rc.1"), + Version.parse("1.0.0") }; for (int i = 0; i < versions.length - 1; i++) { - Assertions.assertTrue(Version.isOlder(versions[i], versions[i + 1])); + Assertions.assertTrue(versions[i].isLessThan(versions[i + 1])); } } @Test public void testBuildNumberPreReleases() { - Version build1 = Version.of("1.0.0-build.1"); - Version build2 = Version.of("1.0.0-build.2"); - Version build10 = Version.of("1.0.0-build.10"); + Version build1 = Version.parse("1.0.0-build.1"); + Version build2 = Version.parse("1.0.0-build.2"); + Version build10 = Version.parse("1.0.0-build.10"); - Assertions.assertTrue(Version.isOlder(build1, build2)); - Assertions.assertTrue(Version.isOlder(build2, build10)); + Assertions.assertTrue(build1.isLessThan(build2)); + Assertions.assertTrue(build2.isLessThan(build10)); } } @@ -447,19 +447,19 @@ public void testBuildNumberPreReleases() { class StandardComplianceTests { @Test public void testStartWithV() { - Assertions.assertDoesNotThrow(() -> Version.of("v1.0.0")); + Assertions.assertDoesNotThrow(() -> Version.parse("v1.0.0")); } @Test public void testNoLeadingZeros() { - Assertions.assertThrows(VersionParseException.class, () -> Version.of("01.0.0")); - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.00.0")); - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.0.00")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("01.0.0")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.00.0")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.0.00")); } @Test public void testLeadingZerosInPreRelease() { - Assertions.assertThrows(VersionParseException.class, () -> Version.of("1.0.0-01")); + Assertions.assertThrows(VersionParseException.class, () -> Version.parse("1.0.0-01")); } } } \ No newline at end of file From 67f16cc09e5c62e93053ab1063714a2cb2a6fe68 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 11:58:21 +0200 Subject: [PATCH 05/13] test: add test coverage for all new features --- .../javasemver/AboveBelowTest.java | 14 ++++++ .../javasemver/BuildOrderingTest.java | 16 +++++++ .../javasemver/CleanPrefixTest.java | 13 +++++ .../javasemver/CoerceCleanTest.java | 28 +++++++++++ .../javasemver/CoerceParityTest.java | 38 +++++++++++++++ .../javasemver/ConstraintTest.java | 26 ++++++++++ .../javasemver/DiffParityTest.java | 45 ++++++++++++++++++ .../javasemver/DifferenceTest.java | 17 +++++++ .../javasemver/GtrLtrParityTest.java | 47 +++++++++++++++++++ .../javasemver/IncrementTest.java | 26 ++++++++++ .../javasemver/IntersectsTest.java | 18 +++++++ .../javasemver/MaxMinSatisfyingTest.java | 24 ++++++++++ .../javasemver/MinVersionTest.java | 28 +++++++++++ .../javasemver/ParityComparisonTest.java | 40 ++++++++++++++++ .../javasemver/ParityIncrementTest.java | 44 +++++++++++++++++ .../javasemver/ParityRangeExcludeTest.java | 30 ++++++++++++ .../javasemver/ParityRangeIncludeTest.java | 32 +++++++++++++ .../javasemver/ParseTrimTest.java | 18 +++++++ .../javasemver/RangeBuildMetadataTest.java | 33 +++++++++++++ .../javasemver/RangeCoreTest.java | 29 ++++++++++++ .../javasemver/RangeHyphenTest.java | 28 +++++++++++ .../javasemver/RangePrereleaseTest.java | 17 +++++++ .../javasemver/RangeTildeCaretTest.java | 27 +++++++++++ .../milkdrinkers/javasemver/RangeXTest.java | 26 ++++++++++ .../javasemver/SatisfiesTest.java | 18 +++++++ .../milkdrinkers/javasemver/SimplifyTest.java | 39 +++++++++++++++ .../javasemver/SubsetPrereleaseTest.java | 23 +++++++++ .../milkdrinkers/javasemver/SubsetTest.java | 19 ++++++++ .../javasemver/VersionComparisonTest.java | 31 ++++++++++++ .../javasemver/VersionLooseParseTest.java | 18 +++++++ .../javasemver/VersionParseTest.java | 34 ++++++++++++++ .../javasemver/VersionValueTest.java | 36 ++++++++++++++ .../javasemver/internal/IntervalTest.java | 35 ++++++++++++++ 33 files changed, 917 insertions(+) create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java new file mode 100644 index 0000000..ecae461 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/AboveBelowTest.java @@ -0,0 +1,14 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class AboveBelowTest { + @Test + void aboveAndBelow() { + Assertions.assertTrue(Version.parse("3.0.0").isAbove(Range.parse("^1.0.0"))); + Assertions.assertTrue(Version.parse("0.5.0").isBelow(Range.parse("^1.0.0"))); + Assertions.assertFalse(Version.parse("1.5.0").isAbove(Range.parse("^1.0.0"))); + Assertions.assertFalse(Version.parse("1.5.0").isBelow(Range.parse("^1.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java new file mode 100644 index 0000000..d3da097 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/BuildOrderingTest.java @@ -0,0 +1,16 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class BuildOrderingTest { + /** + * Compares build identifiers with leading zeros vs non leading zeros + */ + @Test + void leadingZerosCompareNumerically() { + Assertions.assertEquals(0, Version.BUILD_AWARE.compare(Version.parse("1.0.0+007"), Version.parse("1.0.0+7"))); + Assertions.assertTrue(Version.BUILD_AWARE.compare(Version.parse("1.0.0+007"), Version.parse("1.0.0+10")) < 0); + Assertions.assertTrue(Version.BUILD_AWARE.compare(Version.parse("1.0.0+010"), Version.parse("1.0.0+9")) > 0); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java new file mode 100644 index 0000000..0be7dc7 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/CleanPrefixTest.java @@ -0,0 +1,13 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CleanPrefixTest { + @Test + void cleanStripsLeadingEqualsAndVRun() { + Assertions.assertEquals("1.2.3", Version.clean("==v1.2.3").get()); + Assertions.assertEquals("1.2.3", Version.clean("v=1.2.3").get()); + Assertions.assertEquals("1.2.3", Version.clean(" =v1.2.3 ").get()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java new file mode 100644 index 0000000..7c9b21d --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceCleanTest.java @@ -0,0 +1,28 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CoerceCleanTest { + @Test + void coerceExtractsVersions() { + Assertions.assertEquals(Version.of(1, 2, 0), Version.coerce("v1.2").get()); + Assertions.assertEquals(Version.of(1, 2, 3), Version.coerce("app-1.2.3.jar").get()); + Assertions.assertEquals(Version.of(4, 0, 0), Version.coerce("release 4").get()); + Assertions.assertFalse(Version.coerce("no numbers here").isPresent()); + } + + @Test + void cleanNormalizesValidStrings() { + Assertions.assertEquals("1.2.3", Version.clean(" =v1.2.3 ").get()); + Assertions.assertFalse(Version.clean("not a version").isPresent()); + } + + /** + * Coercion fails on overflowing numbers returns empty optional. + */ + @Test + void coerceOverflowingNumber() { + Assertions.assertFalse(Version.coerce("build-99999999999999999999.jar").isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java new file mode 100644 index 0000000..cdca4ac --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/CoerceParityTest.java @@ -0,0 +1,38 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CoerceParityTest { + private static String coerce(String s) { + return Version.coerce(s).map(Version::toString).orElse(null); + } + + @Test + void matchesNodeCoerce() { + Assertions.assertEquals("1.2.0", coerce("v1.2")); + Assertions.assertEquals("1.2.3", coerce("1.2.3.4.5")); + Assertions.assertEquals("2.3.0", coerce(".2.3")); + Assertions.assertEquals("10.0.0", coerce("10.0.0.0")); + Assertions.assertEquals("1.0.0", coerce("a1b2c3")); + Assertions.assertEquals("2.3.0", coerce("version 2.3")); + Assertions.assertEquals("1.2.0", coerce("1.2.x")); + } + + @Test + void leadingZerosMakeCoerceFail() { + Assertions.assertNull(coerce("01.02.03")); + } + + @Test + void overlongNumbersAreSkipped() { + Assertions.assertEquals("5.6.0", coerce("123456789012345678.5.6")); + Assertions.assertEquals("0.0.0", coerce("99999999999999999999.0.0")); + } + + @Test + void noDigitsYieldsEmpty() { + Assertions.assertNull(coerce("x.y.z")); + Assertions.assertNull(coerce("no numbers here")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java new file mode 100644 index 0000000..531e146 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ConstraintTest.java @@ -0,0 +1,26 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ConstraintTest { + @Test + void parsesAndTests() { + Constraint c = Constraint.parse(">=1.2.3"); + Assertions.assertTrue(c.contains(Version.parse("1.2.3"))); + Assertions.assertTrue(c.contains(Version.parse("2.0.0"))); + Assertions.assertFalse(c.contains(Version.parse("1.0.0"))); + Assertions.assertEquals(">=1.2.3", c.toString()); + } + + @Test + void bareVersionIsEquality() { + Assertions.assertTrue(Constraint.parse("1.2.3").contains(Version.parse("1.2.3"))); + Assertions.assertFalse(Constraint.parse("1.2.3").contains(Version.parse("1.2.4"))); + } + + @Test + void wildcardMatchesAnyRelease() { + Assertions.assertTrue(Constraint.any().contains(Version.parse("9.9.9"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java new file mode 100644 index 0000000..b1294d1 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/DiffParityTest.java @@ -0,0 +1,45 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DiffParityTest { + @Test + void prereleaseOfMajorMilestoneToReleaseIsMajor() { + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0-a").difference(Version.parse("1.0.0")).get()); + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0-a").difference(Version.parse("1.1.0")).get()); + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0-a").difference(Version.parse("2.0.0")).get()); + } + + @Test + void prereleaseToSameCoreReleaseUsesLowestChangedComponent() { + Assertions.assertEquals(ReleaseType.MINOR, Version.parse("1.2.0-a").difference(Version.parse("1.2.0")).get()); + Assertions.assertEquals(ReleaseType.PATCH, Version.parse("1.2.3-a").difference(Version.parse("1.2.3")).get()); + } + + @Test + void prereleaseToHigherReleaseUsesPlainReleaseType() { + Assertions.assertEquals(ReleaseType.MINOR, Version.parse("1.2.0-a").difference(Version.parse("1.3.0")).get()); + Assertions.assertEquals(ReleaseType.PATCH, Version.parse("1.2.3-a").difference(Version.parse("1.2.4")).get()); + } + + @Test + void prePrefixOnlyWhenHigherVersionIsPrerelease() { + Assertions.assertEquals(ReleaseType.PREMAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0-a")).get()); + Assertions.assertEquals(ReleaseType.PREMINOR, Version.parse("1.0.0").difference(Version.parse("1.1.0-a")).get()); + Assertions.assertEquals(ReleaseType.PREPATCH, Version.parse("1.0.0").difference(Version.parse("1.0.1-a")).get()); + } + + @Test + void differenceIsSymmetric() { + Assertions.assertEquals(Version.parse("1.0.0-a").difference(Version.parse("1.0.0")), Version.parse("1.0.0").difference(Version.parse("1.0.0-a"))); + } + + @Test + void plainAndPrereleaseOnlyCasesUnchanged() { + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0")).get()); + Assertions.assertEquals(ReleaseType.PRERELEASE, Version.parse("1.0.0-a").difference(Version.parse("1.0.0-b")).get()); + Assertions.assertFalse(Version.parse("1.2.3").difference(Version.parse("1.2.3")).isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java new file mode 100644 index 0000000..62b8b3f --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/DifferenceTest.java @@ -0,0 +1,17 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DifferenceTest { + @Test + void classifiesDifference() { + Assertions.assertEquals(ReleaseType.MAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0")).get()); + Assertions.assertEquals(ReleaseType.MINOR, Version.parse("1.0.0").difference(Version.parse("1.1.0")).get()); + Assertions.assertEquals(ReleaseType.PATCH, Version.parse("1.0.0").difference(Version.parse("1.0.1")).get()); + Assertions.assertEquals(ReleaseType.PREMAJOR, Version.parse("1.0.0").difference(Version.parse("2.0.0-a")).get()); + Assertions.assertEquals(ReleaseType.PRERELEASE, Version.parse("1.0.0-a").difference(Version.parse("1.0.0-b")).get()); + Assertions.assertFalse(Version.parse("1.0.0").difference(Version.parse("1.0.0")).isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java new file mode 100644 index 0000000..0df672b --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/GtrLtrParityTest.java @@ -0,0 +1,47 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class GtrLtrParityTest { + private static boolean gtr(String v, String r) { + return Version.parse(v).isAbove(Range.parse(r)); + } + + private static boolean ltr(String v, String r) { + return Version.parse(v).isBelow(Range.parse(r)); + } + + @Test + void prereleaseInsideRangeIsReportedBothAboveAndBelow() { + Assertions.assertTrue(gtr("1.5.0-alpha", "^1.0.0")); + Assertions.assertTrue(ltr("1.5.0-alpha", "^1.0.0")); + } + + @Test + void ordinaryCasesMatchNode() { + Assertions.assertTrue(gtr("3.0.0", "^1.0.0")); + Assertions.assertFalse(ltr("3.0.0", "^1.0.0")); + Assertions.assertFalse(gtr("0.5.0", "^1.0.0")); + Assertions.assertTrue(ltr("0.5.0", "^1.0.0")); + Assertions.assertTrue(gtr("2.0.0", "^1.0.0")); + Assertions.assertTrue(gtr("2.0.0-0", "^1.0.0")); + Assertions.assertTrue(gtr("1.3.0", "~1.2.3")); + Assertions.assertFalse(gtr("1.2.2", "~1.2.3")); + Assertions.assertTrue(ltr("1.2.2", "~1.2.3")); + } + + @Test + void satisfyingVersionIsNeitherAboveNorBelow() { + Assertions.assertFalse(gtr("2.0.0", ">=1.0.0")); + Assertions.assertFalse(ltr("2.0.0", ">=1.0.0")); + Assertions.assertFalse(gtr("0.5.0", "<2.0.0")); + } + + @Test + void versionInGapBetweenOrSetsIsNeitherAboveNorBelow() { + Assertions.assertFalse(gtr("2.0.0", "1.x || 3.x")); + Assertions.assertFalse(ltr("2.0.0", "1.x || 3.x")); + Assertions.assertFalse(gtr("2.5.0", "1.x || 3.x")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java new file mode 100644 index 0000000..02b0e77 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/IncrementTest.java @@ -0,0 +1,26 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class IncrementTest { + @Test + void plainIncrements() { + Assertions.assertEquals("2.0.0", Version.parse("1.2.3").increment(ReleaseType.MAJOR).toString()); + Assertions.assertEquals("1.3.0", Version.parse("1.2.3").increment(ReleaseType.MINOR).toString()); + Assertions.assertEquals("1.2.4", Version.parse("1.2.3").increment(ReleaseType.PATCH).toString()); + } + + @Test + void prereleaseIncrements() { + Assertions.assertEquals("1.2.4-0", Version.parse("1.2.3").increment(ReleaseType.PRERELEASE).toString()); + Assertions.assertEquals("1.2.4-alpha.0", Version.parse("1.2.3").increment(ReleaseType.PREPATCH, "alpha").toString()); + Assertions.assertEquals("1.2.3-alpha.2", Version.parse("1.2.3-alpha.1").increment(ReleaseType.PRERELEASE, "alpha").toString()); + } + + @Test + void majorOfPrereleaseAtZeroDropsPrerelease() { + Assertions.assertEquals("2.0.0", Version.parse("2.0.0-alpha.1").increment(ReleaseType.MAJOR).toString()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java new file mode 100644 index 0000000..d3e1ae4 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/IntersectsTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class IntersectsTest { + @Test + void overlapping() { + Assertions.assertTrue(Range.parse(">=1.0.0 <2.0.0").intersects(Range.parse("^1.5.0"))); + Assertions.assertTrue(Range.parse("*").intersects(Range.parse("^1.0.0"))); + } + + @Test + void disjoint() { + Assertions.assertFalse(Range.parse("^1.0.0").intersects(Range.parse("^2.0.0"))); + Assertions.assertFalse(Range.parse("<1.0.0").intersects(Range.parse(">=2.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java new file mode 100644 index 0000000..1408d71 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/MaxMinSatisfyingTest.java @@ -0,0 +1,24 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +class MaxMinSatisfyingTest { + private final List versions = Arrays.asList( + Version.parse("1.0.0"), Version.parse("1.2.0"), + Version.parse("1.9.0"), Version.parse("2.0.0")); + + @Test + void maxAndMin() { + Assertions.assertEquals(Version.parse("1.9.0"), Range.maxSatisfying(versions, "^1.0.0").get()); + Assertions.assertEquals(Version.parse("1.0.0"), Range.minSatisfying(versions, "^1.0.0").get()); + } + + @Test + void emptyWhenNoneMatch() { + Assertions.assertFalse(Range.maxSatisfying(versions, ">=3.0.0").isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java new file mode 100644 index 0000000..e9af9a0 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/MinVersionTest.java @@ -0,0 +1,28 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class MinVersionTest { + @Test + void lowestSatisfying() { + Assertions.assertEquals(Version.parse("1.2.0"), Range.parse("^1.2.0").minVersion().get()); + Assertions.assertEquals(Version.parse("1.2.4"), Range.parse(">1.2.3").minVersion().get()); + Assertions.assertEquals(Version.parse("0.0.0"), Range.parse("*").minVersion().get()); + } + + @Test + void emptyRangeHasNoMin() { + Assertions.assertFalse(Range.parse(">=2.0.0 <1.0.0").minVersion().isPresent()); + } + + @Test + void exclusiveLowerBoundWithPrereleaseAppendsZeroIdentifier() { + Assertions.assertEquals(Version.parse("1.2.3-alpha.0"), Range.parse(">1.2.3-alpha").minVersion().get()); + } + + @Test + void exclusiveLowerBoundWithoutPrereleaseStillBumpsPatch() { + Assertions.assertEquals(Version.parse("1.2.4"), Range.parse(">1.2.3").minVersion().get()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java new file mode 100644 index 0000000..ebcc7f6 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityComparisonTest.java @@ -0,0 +1,40 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +class ParityComparisonTest { + @ParameterizedTest(name = "[{index}] \"{0}\" < \"{1}\"") + @MethodSource("pairs") + void lowerIsLessThanHigher(String lower, String higher) { + final Version lowerVersion = Version.parse(lower); + final Version higherVersion = Version.parse(higher); + + Assertions.assertTrue(lowerVersion.isLessThan(higherVersion), () -> "expected \"" + lower + "\" to be less than \"" + higher + "\""); + Assertions.assertTrue(higherVersion.isGreaterThan(lowerVersion), () -> "expected \"" + higher + "\" to be greater than \"" + lower + "\""); + } + + static Stream pairs() { + return Stream.of( + Arguments.of("1.0.0-alpha", "1.0.0-alpha.1"), + Arguments.of("1.0.0-alpha.1", "1.0.0-alpha.beta"), + Arguments.of("1.0.0-alpha.beta", "1.0.0-beta"), + Arguments.of("1.0.0-beta", "1.0.0-beta.2"), + Arguments.of("1.0.0-beta.2", "1.0.0-beta.11"), + Arguments.of("1.0.0-beta.11", "1.0.0-rc.1"), + Arguments.of("1.0.0-rc.1", "1.0.0"), + Arguments.of("1.0.0", "2.0.0"), + Arguments.of("1.0.0-1", "1.0.0-alpha"), + Arguments.of("1.0.0-2", "1.0.0-10"), + Arguments.of("0.0.1", "0.0.2"), + Arguments.of("0.1.0", "0.2.0"), + Arguments.of("1.2.3", "1.2.4"), + Arguments.of("1.2.3", "1.3.0"), + Arguments.of("1.2.3", "2.0.0") + ); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java new file mode 100644 index 0000000..b370b06 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityIncrementTest.java @@ -0,0 +1,44 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +class ParityIncrementTest { + @ParameterizedTest(name = "[{index}] \"{0}\".increment({1}) = \"{2}\"") + @MethodSource("noIdentifierCases") + void incrementWithoutIdentifier(String input, ReleaseType release, String expected) { + Assertions.assertEquals(expected, Version.parse(input).increment(release).toString()); + } + + @ParameterizedTest(name = "[{index}] \"{0}\".increment({1}, \"{2}\") = \"{3}\"") + @MethodSource("withIdentifierCases") + void incrementWithIdentifier(String input, ReleaseType release, String identifier, String expected) { + Assertions.assertEquals(expected, Version.parse(input).increment(release, identifier).toString()); + } + + static Stream noIdentifierCases() { + return Stream.of( + Arguments.of("1.2.3", ReleaseType.MAJOR, "2.0.0"), + Arguments.of("1.2.3", ReleaseType.MINOR, "1.3.0"), + Arguments.of("1.2.3", ReleaseType.PATCH, "1.2.4"), + Arguments.of("1.2.3", ReleaseType.PREMAJOR, "2.0.0-0"), + Arguments.of("1.2.3", ReleaseType.PREMINOR, "1.3.0-0"), + Arguments.of("1.2.3", ReleaseType.PREPATCH, "1.2.4-0"), + Arguments.of("1.2.3", ReleaseType.PRERELEASE, "1.2.4-0"), + Arguments.of("1.2.3-alpha.1", ReleaseType.PRERELEASE, "1.2.3-alpha.2"), + Arguments.of("2.0.0-alpha.1", ReleaseType.MAJOR, "2.0.0") + ); + } + + static Stream withIdentifierCases() { + return Stream.of( + Arguments.of("1.2.3", ReleaseType.PREPATCH, "alpha", "1.2.4-alpha.0"), + Arguments.of("1.2.3-alpha.1", ReleaseType.PRERELEASE, "alpha", "1.2.3-alpha.2") + ); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java new file mode 100644 index 0000000..7d4fcc4 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeExcludeTest.java @@ -0,0 +1,30 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ParityRangeExcludeTest { + @ParameterizedTest(name = "[{index}] \"{1}\" does not satisfy \"{0}\"") + @CsvSource({ + "^1.2.3, 2.0.0", + "^1.2.3, 1.2.2", + "^0.2.3, 0.3.0", + "~1.2.3, 1.3.0", + "~1.2.3, 1.2.2", + "1.2.3 - 2.3.4, 2.3.5", + "1.2.3 - 2.3.4, 1.2.2", + "1.2.3 - 2.3, 2.4.0", + "1.2 - 2.3.4, 1.1.9", + "1.x, 2.0.0", + "1.2.x, 1.3.0", + "1.2.x, 1.1.9", + ">=1.2.3 <2.0.0, 2.0.0", + ">=1.2.3 <2.0.0, 1.0.0", + "1.2.3 || >=2.0.0, 1.5.0", + ">=1.0.0 <2.0.0, 1.2.3-alpha.4" + }) + void doesNotSatisfy(String range, String version) { + Assertions.assertFalse(Version.parse(version).satisfies(range), () -> "expected \"" + version + "\" NOT to satisfy \"" + range + "\""); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java new file mode 100644 index 0000000..aa9d210 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParityRangeIncludeTest.java @@ -0,0 +1,32 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class ParityRangeIncludeTest { + @ParameterizedTest(name = "[{index}] \"{1}\" satisfies \"{0}\"") + @CsvSource({ + "^1.2.3, 1.2.3", + "^1.2.3, 1.9.9", + "^0.2.3, 0.2.9", + "~1.2.3, 1.2.3", + "~1.2.3, 1.2.9", + "1.2.3 - 2.3.4, 1.2.3", + "1.2.3 - 2.3.4, 2.3.4", + "1.2.3 - 2.3, 2.3.9", + "1.2 - 2.3.4, 1.2.0", + "1.x, 1.9.9", + "1.2.x, 1.2.5", + "*, 3.1.4", + "1, 1.4.2", + "1.2, 1.2.7", + ">=1.0.0 <2.0.0, 1.5.0", + "1.2.3 || >=2.0.0, 1.2.3", + "1.2.3 || >=2.0.0, 2.5.0", + ">=1.2.3-alpha <2.0.0, 1.2.3-alpha.4" + }) + void satisfies(String range, String version) { + Assertions.assertTrue(Version.parse(version).satisfies(range), () -> "expected \"" + version + "\" to satisfy \"" + range + "\""); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java new file mode 100644 index 0000000..d59d975 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/ParseTrimTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ParseTrimTest { + @Test + void strictParseTrimsSurroundingWhitespace() { + Assertions.assertEquals(Version.of(1, 2, 3), Version.parse(" 1.2.3 ")); + Assertions.assertEquals(Version.of(1, 2, 3), Version.parse("\t1.2.3\n")); + Assertions.assertEquals(Version.of(1, 2, 3), Version.parse(" v1.2.3 ")); + } + + @Test + void isValidAcceptsPaddedVersion() { + Assertions.assertTrue(Version.isValid(" 1.2.3 ")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java new file mode 100644 index 0000000..4ab2342 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeBuildMetadataTest.java @@ -0,0 +1,33 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Build metadata never affects matching in ranges so range comparators with it must parse and behave as if it were absent. + */ +class RangeBuildMetadataTest { + @Test + void tildeToleratesBuildMetadata() { + Assertions.assertTrue(Version.parse("1.2.9").satisfies("~1.2.3+build")); + Assertions.assertFalse(Version.parse("1.3.0").satisfies("~1.2.3+build")); + } + + @Test + void caretToleratesBuildMetadata() { + Assertions.assertTrue(Version.parse("1.9.0").satisfies("^1.2.3+build")); + Assertions.assertFalse(Version.parse("2.0.0").satisfies("^1.2.3+build")); + } + + @Test + void hyphenToleratesBuildMetadataOnEitherBound() { + Assertions.assertTrue(Version.parse("2.3.4").satisfies("1.2.3+build - 2.3.4")); + Assertions.assertTrue(Version.parse("2.3.4").satisfies("1.2.3 - 2.3.4+build")); + Assertions.assertFalse(Version.parse("2.3.5").satisfies("1.2.3 - 2.3.4+build")); + } + + @Test + void caretWithPrereleaseAndBuildMetadata() { + Assertions.assertTrue(Version.parse("1.2.3-beta.2").satisfies("^1.2.3-beta+build")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java new file mode 100644 index 0000000..533f94f --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeCoreTest.java @@ -0,0 +1,29 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeCoreTest { + @Test + void andSet() { + Range r = Range.parse(">=1.2.3 <2.0.0"); + Assertions.assertTrue(r.contains(Version.parse("1.5.0"))); + Assertions.assertFalse(r.contains(Version.parse("2.0.0"))); + Assertions.assertFalse(r.contains(Version.parse("1.0.0"))); + } + + @Test + void orSets() { + Range r = Range.parse("1.2.3 || >=2.0.0"); + Assertions.assertTrue(r.contains(Version.parse("1.2.3"))); + Assertions.assertTrue(r.contains(Version.parse("3.0.0"))); + Assertions.assertFalse(r.contains(Version.parse("1.5.0"))); + } + + @Test + void starIsAny() { + Assertions.assertTrue(Range.parse("*").isAny()); + Assertions.assertTrue(Range.parse("").isAny()); + Assertions.assertTrue(Range.parse("*").contains(Version.parse("1.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java new file mode 100644 index 0000000..dbaec0b --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeHyphenTest.java @@ -0,0 +1,28 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeHyphenTest { + @Test + void fullHyphen() { + Range r = Range.parse("1.2.3 - 2.3.4"); + Assertions.assertTrue(r.contains(Version.parse("1.2.3"))); + Assertions.assertTrue(r.contains(Version.parse("2.3.4"))); + Assertions.assertFalse(r.contains(Version.parse("2.3.5"))); + } + + @Test + void partialUpperBoundExpands() { + Range r = Range.parse("1.2.3 - 2.3"); + Assertions.assertTrue(r.contains(Version.parse("2.3.9"))); + Assertions.assertFalse(r.contains(Version.parse("2.4.0"))); + } + + @Test + void partialLowerBound() { + Range r = Range.parse("1.2 - 2.3.4"); + Assertions.assertTrue(r.contains(Version.parse("1.2.0"))); + Assertions.assertFalse(r.contains(Version.parse("1.1.9"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java new file mode 100644 index 0000000..df2c63c --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangePrereleaseTest.java @@ -0,0 +1,17 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangePrereleaseTest { + @Test + void prereleaseMatchesOnlyWhenNamed() { + Assertions.assertTrue(Range.parse(">=1.2.3-alpha <2.0.0").contains(Version.parse("1.2.3-alpha.4"))); + Assertions.assertFalse(Range.parse(">=1.0.0 <2.0.0").contains(Version.parse("1.2.3-alpha.4"))); + } + + @Test + void stableUnaffected() { + Assertions.assertTrue(Range.parse(">=1.0.0 <2.0.0").contains(Version.parse("1.5.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java new file mode 100644 index 0000000..373ccf7 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeTildeCaretTest.java @@ -0,0 +1,27 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeTildeCaretTest { + @Test + void tilde() { + Range r = Range.parse("~1.2.3"); + Assertions.assertTrue(r.contains(Version.parse("1.2.9"))); + Assertions.assertFalse(r.contains(Version.parse("1.3.0"))); + } + + @Test + void caretAboveZero() { + Range r = Range.parse("^1.2.3"); + Assertions.assertTrue(r.contains(Version.parse("1.9.0"))); + Assertions.assertFalse(r.contains(Version.parse("2.0.0"))); + } + + @Test + void caretZeroMinor() { + Range r = Range.parse("^0.2.3"); + Assertions.assertTrue(r.contains(Version.parse("0.2.9"))); + Assertions.assertFalse(r.contains(Version.parse("0.3.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java new file mode 100644 index 0000000..cb513a6 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/RangeXTest.java @@ -0,0 +1,26 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class RangeXTest { + @Test + void minorWildcard() { + Range r = Range.parse("1.x"); + Assertions.assertTrue(r.contains(Version.parse("1.9.9"))); + Assertions.assertFalse(r.contains(Version.parse("2.0.0"))); + } + + @Test + void patchWildcard() { + Range r = Range.parse("1.2.x"); + Assertions.assertTrue(r.contains(Version.parse("1.2.9"))); + Assertions.assertFalse(r.contains(Version.parse("1.3.0"))); + } + + @Test + void partialMeansWildcard() { + Assertions.assertTrue(Range.parse("1").contains(Version.parse("1.4.2"))); + Assertions.assertTrue(Range.parse("1.2").contains(Version.parse("1.2.7"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java new file mode 100644 index 0000000..6d43eaf --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SatisfiesTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SatisfiesTest { + @Test + void satisfiesFromVersionSide() { + Assertions.assertTrue(Version.parse("1.2.3").satisfies("^1.0.0")); + Assertions.assertTrue(Version.parse("1.2.3").satisfies(Range.parse("~1.2.0"))); + Assertions.assertFalse(Version.parse("2.0.0").satisfies("^1.0.0")); + } + + @Test + void rangeParseLooseAcceptsVPrefixedBounds() { + Assertions.assertTrue(Range.parseLoose(">=v1.0.0").contains(Version.parse("1.2.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java new file mode 100644 index 0000000..6fba613 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SimplifyTest.java @@ -0,0 +1,39 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +class SimplifyTest { + @Test + void collapsesContiguousRun() { + List versions = Arrays.asList( + Version.parse("1.0.0"), + Version.parse("1.1.0"), + Version.parse("1.2.0"), + Version.parse("1.3.0"), + Version.parse("1.4.0") + ); + Range simplified = Range.simplify(versions, Range.parse(">=1.0.0 <2.0.0")); + for (Version v : versions) { + Assertions.assertEquals(Range.parse(">=1.0.0 <2.0.0").contains(v), simplified.contains(v), "membership must be preserved for " + v); + } + } + + @Test + void equivalenceOverTheSet() { + List versions = Arrays.asList( + Version.parse("0.9.0"), + Version.parse("1.0.0"), + Version.parse("1.5.0"), + Version.parse("2.0.0") + ); + Range input = Range.parse(">=1.0.0 <2.0.0"); + Range simplified = Range.simplify(versions, input); + for (Version v : versions) { + Assertions.assertEquals(input.contains(v), simplified.contains(v)); + } + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java new file mode 100644 index 0000000..7967dbb --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetPrereleaseTest.java @@ -0,0 +1,23 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SubsetPrereleaseTest { + @Test + void prereleaseBoundRequiresMatchingTupleInDominator() { + Assertions.assertFalse(Range.parse(">=1.2.3-pre").isSubsetOf(Range.parse(">=1.0.0"))); + Assertions.assertFalse(Range.parse("^1.2.3-beta").isSubsetOf(Range.parse(">=1.0.0 <2.0.0"))); + } + + @Test + void prereleaseBoundSubsetWhenDominatorNamesSameTuple() { + Assertions.assertTrue(Range.parse(">=1.2.3-pre <2.0.0").isSubsetOf(Range.parse(">=1.2.3-pre <3.0.0"))); + } + + @Test + void caretUpperZeroPrereleaseBoundDoesNotTriggerRequirement() { + Assertions.assertTrue(Range.parse("^1.2.3").isSubsetOf(Range.parse(">=1.0.0 <2.0.0"))); + Assertions.assertTrue(Range.parse("~1.2.3").isSubsetOf(Range.parse(">=1.2.0 <1.3.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java new file mode 100644 index 0000000..9eeae78 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/SubsetTest.java @@ -0,0 +1,19 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SubsetTest { + @Test + void containment() { + Assertions.assertTrue(Range.parse("^1.2.0").isSubsetOf(Range.parse(">=1.0.0 <2.0.0"))); + Assertions.assertTrue(Range.parse(">=1.5.0 <1.8.0").isSubsetOf(Range.parse("^1.0.0"))); + Assertions.assertTrue(Range.parse("^1.0.0").isSubsetOf(Range.parse("*"))); + } + + @Test + void notContained() { + Assertions.assertFalse(Range.parse("^1.0.0").isSubsetOf(Range.parse(">=1.5.0 <2.0.0"))); + Assertions.assertFalse(Range.parse("*").isSubsetOf(Range.parse("^1.0.0"))); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java new file mode 100644 index 0000000..67fe860 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionComparisonTest.java @@ -0,0 +1,31 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class VersionComparisonTest { + @Test + void precedenceOrdering() { + Assertions.assertTrue(Version.parse("1.0.0-alpha").isLessThan(Version.parse("1.0.0-alpha.1"))); + Assertions.assertTrue(Version.parse("1.0.0-rc.1").isLessThan(Version.parse("1.0.0"))); + Assertions.assertTrue(Version.parse("2.0.0").isGreaterThan(Version.parse("1.9.9"))); + } + + @Test + void predicates() { + Version a = Version.parse("1.2.3"); + Assertions.assertTrue(a.isAtLeast(Version.parse("1.2.3"))); + Assertions.assertTrue(a.isAtMost(Version.parse("1.2.3"))); + Assertions.assertTrue(a.isEqualTo(Version.parse("1.2.3"))); + Assertions.assertFalse(a.isGreaterThan(Version.parse("1.2.3"))); + } + + @Test + void buildMetadataIgnoredForPrecedenceButUsedByBuildAware() { + Version a = Version.parse("1.0.0+build.1"); + Version b = Version.parse("1.0.0+build.2"); + Assertions.assertEquals(0, a.compareTo(b)); + Assertions.assertTrue(a.isEqualTo(b)); + Assertions.assertTrue(Version.BUILD_AWARE.compare(a, b) < 0); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java new file mode 100644 index 0000000..299b78d --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionLooseParseTest.java @@ -0,0 +1,18 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class VersionLooseParseTest { + @Test + void tolerantOfWhitespacePrefixAndLeadingZeros() { + Assertions.assertEquals(Version.of(1, 2, 3), Version.parseLoose(" =v1.2.3 ")); + Assertions.assertEquals(Version.of(1, 2, 3), Version.parseLoose("01.02.03")); + Assertions.assertEquals("1.2.3-beta", Version.parseLoose("v1.2.3-beta").toString()); + } + + @Test + void strictStillRejectsLeadingZeros() { + Assertions.assertFalse(Version.isValid("01.02.03")); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java new file mode 100644 index 0000000..bdcc331 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionParseTest.java @@ -0,0 +1,34 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.exception.VersionParseException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class VersionParseTest { + @Test + void parsesFullVersion() { + Version v = Version.parse("1.2.3-alpha.1+build.7"); + Assertions.assertEquals("1.2.3-alpha.1+build.7", v.toString()); + } + + @Test + void stripsLeadingV() { + Assertions.assertEquals(Version.of(1, 0, 0), Version.parse("v1.0.0")); + } + + @ParameterizedTest + @ValueSource(strings = {"1.02.03", "1.a.3", "1.2", "1.2.3-", "1.2.3-alpha..1", "1.0.0-01"}) + void rejectsInvalid(String s) { + Assertions.assertThrows(VersionParseException.class, () -> Version.parse(s)); + Assertions.assertFalse(Version.isValid(s)); + Assertions.assertFalse(Version.parseOptional(s).isPresent()); + } + + @Test + void optionalAndValidAgreeOnGood() { + Assertions.assertTrue(Version.isValid("1.2.3")); + Assertions.assertTrue(Version.parseOptional("1.2.3").isPresent()); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java new file mode 100644 index 0000000..a9c6aff --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/VersionValueTest.java @@ -0,0 +1,36 @@ +package io.github.milkdrinkers.javasemver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +class VersionValueTest { + @Test + void exposesComponents() { + Version v = Version.of(1, 2, 3, "alpha.1", "build.7"); + Assertions.assertEquals(1, v.getMajor()); + Assertions.assertEquals(2, v.getMinor()); + Assertions.assertEquals(3, v.getPatch()); + Assertions.assertEquals("alpha.1", v.getPreRelease()); + Assertions.assertEquals(Arrays.asList("alpha", "1"), v.getPreReleaseIdentifiers()); + Assertions.assertEquals("build.7", v.getBuildMetadata()); + Assertions.assertTrue(v.hasPreRelease()); + Assertions.assertTrue(v.hasBuildMetadata()); + Assertions.assertEquals("1.2.3-alpha.1+build.7", v.toString()); + } + + @Test + void identifiersListIsUnmodifiable() { + Version v = Version.of(1, 0, 0, "a.b"); + Assertions.assertThrows(UnsupportedOperationException.class, () -> v.getPreReleaseIdentifiers().add("c")); + } + + @Test + void structuralEqualityIncludesBuildMetadata() { + Version a = Version.of(1, 0, 0, "", "build.1"); + Version b = Version.of(1, 0, 0, "", "build.2"); + Assertions.assertNotEquals(a, b); + Assertions.assertEquals(Version.of(1, 0, 0), Version.of(1, 0, 0)); + } +} diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java new file mode 100644 index 0000000..9f72e0e --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/internal/IntervalTest.java @@ -0,0 +1,35 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Version; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +class IntervalTest { + @Test + void combinesBounds() { + Interval i = Interval.of(Arrays.asList(Constraint.parse(">=1.2.0"), Constraint.parse("<2.0.0"))); + Assertions.assertEquals(Version.parse("1.2.0"), i.getLower()); + Assertions.assertTrue(i.isLowerInclusive()); + Assertions.assertEquals(Version.parse("2.0.0"), i.getUpper()); + Assertions.assertFalse(i.isUpperInclusive()); + Assertions.assertFalse(i.isEmpty()); + } + + @Test + void contradictionIsEmpty() { + Interval i = Interval.of(Arrays.asList(Constraint.parse(">=2.0.0"), Constraint.parse("<1.0.0"))); + Assertions.assertTrue(i.isEmpty()); + } + + @Test + void overlapDetection() { + Interval a = Interval.of(Arrays.asList(Constraint.parse(">=1.0.0"), Constraint.parse("<2.0.0"))); + Interval b = Interval.of(Arrays.asList(Constraint.parse(">=1.5.0"), Constraint.parse("<3.0.0"))); + Interval c = Interval.of(Arrays.asList(Constraint.parse(">=2.0.0"), Constraint.parse("<3.0.0"))); + Assertions.assertTrue(Interval.overlaps(a, b)); + Assertions.assertFalse(Interval.overlaps(a, c)); + } +} From 2c18ccb82864618ead856bc2e9623db06573877e Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 12:11:30 +0200 Subject: [PATCH 06/13] test: add node-semver parity tests This ensures parity in results with official node-semver package. --- .../javasemver/NodeParityTest.java | 75 + common/src/test/resources/node_parity.txt | 2183 +++++++++++++++++ 2 files changed, 2258 insertions(+) create mode 100644 common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java create mode 100644 common/src/test/resources/node_parity.txt diff --git a/common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java b/common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java new file mode 100644 index 0000000..c49df27 --- /dev/null +++ b/common/src/test/java/io/github/milkdrinkers/javasemver/NodeParityTest.java @@ -0,0 +1,75 @@ +package io.github.milkdrinkers.javasemver; + +import io.github.milkdrinkers.javasemver.enums.ReleaseType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Test class running tests ensuring full project parity with node-semver 7.8.5. + * A result file is generated from node-semver's tests and stored in test resources as {@code node_parity.txt}, and this test runs the same tests with java-semver and fails on mismatch in results. + */ +class NodeParityTest { + @Test + void matchesNodeSemver() throws Exception { + final List mismatches = new ArrayList<>(); + int checked = 0; + + try (InputStream in = getClass().getResourceAsStream("/node_parity.txt")) { + Assertions.assertNotNull(in, "test resource file /node_parity.txt not found on the test classpath!"); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) + continue; + + final String[] parts = line.split("\t", -1); + final String method = parts[0]; + final String a = parts[1]; + final String b = parts[2]; + final String expected = parts[3]; + + checked++; + String actual; + try { + actual = compute(method, a, b); + } catch (RuntimeException e) { + actual = "THREW:" + e.getClass().getSimpleName(); + } + + if (!expected.equals(actual)) + mismatches.add(method + "(" + a + (b.isEmpty() ? "" : " , " + b) + ") expected=" + expected + " actual=" + actual); + } + } + } + + if (!mismatches.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + sb.append(mismatches.size()).append(" of ").append(checked).append(" node parity checks deviated:\n"); + for (final String m : mismatches) + sb.append(" ").append(m).append('\n'); + Assertions.fail(sb.toString()); + } + } + + private static String compute(String method, String a, String b) { + return switch (method) { + case "satisfies" -> String.valueOf(Version.parse(a).satisfies(b)); + case "gtr" -> String.valueOf(Version.parse(a).isAbove(Range.parse(b))); + case "ltr" -> String.valueOf(Version.parse(a).isBelow(Range.parse(b))); + case "intersects" -> String.valueOf(Range.parse(a).intersects(Range.parse(b))); + case "compare" -> String.valueOf(Integer.signum(Version.parse(a).compareTo(Version.parse(b)))); + case "diff" -> Version.parse(a).difference(Version.parse(b)).map(rt -> rt.name().toLowerCase()).orElse("NULL"); + case "inc" -> Version.parse(a).increment(ReleaseType.valueOf(b.toUpperCase())).toString(); + case "coerce" -> Version.coerce(a).map(Version::toString).orElse("NULL"); + default -> throw new IllegalArgumentException("unknown method: " + method); + }; + } +} diff --git a/common/src/test/resources/node_parity.txt b/common/src/test/resources/node_parity.txt new file mode 100644 index 0000000..030f162 --- /dev/null +++ b/common/src/test/resources/node_parity.txt @@ -0,0 +1,2183 @@ +satisfies 0.0.0 1.2.3 false +gtr 0.0.0 1.2.3 false +ltr 0.0.0 1.2.3 true +satisfies 0.0.0 =1.2.3 false +gtr 0.0.0 =1.2.3 false +ltr 0.0.0 =1.2.3 true +satisfies 0.0.0 >1.2.3 false +gtr 0.0.0 >1.2.3 false +ltr 0.0.0 >1.2.3 true +satisfies 0.0.0 >=1.2.3 false +gtr 0.0.0 >=1.2.3 false +ltr 0.0.0 >=1.2.3 true +satisfies 0.0.0 <1.2.3 true +gtr 0.0.0 <1.2.3 false +ltr 0.0.0 <1.2.3 false +satisfies 0.0.0 <=1.2.3 true +gtr 0.0.0 <=1.2.3 false +ltr 0.0.0 <=1.2.3 false +satisfies 0.0.0 ^1.2.3 false +gtr 0.0.0 ^1.2.3 false +ltr 0.0.0 ^1.2.3 true +satisfies 0.0.0 ~1.2.3 false +gtr 0.0.0 ~1.2.3 false +ltr 0.0.0 ~1.2.3 true +satisfies 0.0.0 ^0.2.3 false +gtr 0.0.0 ^0.2.3 false +ltr 0.0.0 ^0.2.3 true +satisfies 0.0.0 ^0.0.3 false +gtr 0.0.0 ^0.0.3 false +ltr 0.0.0 ^0.0.3 true +satisfies 0.0.0 1.2.x false +gtr 0.0.0 1.2.x false +ltr 0.0.0 1.2.x true +satisfies 0.0.0 1.x false +gtr 0.0.0 1.x false +ltr 0.0.0 1.x true +satisfies 0.0.0 * true +gtr 0.0.0 * false +ltr 0.0.0 * false +satisfies 0.0.0 >=1.2.3 <2.0.0 false +gtr 0.0.0 >=1.2.3 <2.0.0 false +ltr 0.0.0 >=1.2.3 <2.0.0 true +satisfies 0.0.0 1.2.3 - 2.3.4 false +gtr 0.0.0 1.2.3 - 2.3.4 false +ltr 0.0.0 1.2.3 - 2.3.4 true +satisfies 0.0.0 1.2.3 || >=2.0.0 false +gtr 0.0.0 1.2.3 || >=2.0.0 false +ltr 0.0.0 1.2.3 || >=2.0.0 true +satisfies 0.0.0 ^1.2.3-alpha false +gtr 0.0.0 ^1.2.3-alpha false +ltr 0.0.0 ^1.2.3-alpha true +satisfies 0.0.0 ~1.2 false +gtr 0.0.0 ~1.2 false +ltr 0.0.0 ~1.2 true +satisfies 0.0.0 ^1 false +gtr 0.0.0 ^1 false +ltr 0.0.0 ^1 true +satisfies 1.0.0 1.2.3 false +gtr 1.0.0 1.2.3 false +ltr 1.0.0 1.2.3 true +satisfies 1.0.0 =1.2.3 false +gtr 1.0.0 =1.2.3 false +ltr 1.0.0 =1.2.3 true +satisfies 1.0.0 >1.2.3 false +gtr 1.0.0 >1.2.3 false +ltr 1.0.0 >1.2.3 true +satisfies 1.0.0 >=1.2.3 false +gtr 1.0.0 >=1.2.3 false +ltr 1.0.0 >=1.2.3 true +satisfies 1.0.0 <1.2.3 true +gtr 1.0.0 <1.2.3 false +ltr 1.0.0 <1.2.3 false +satisfies 1.0.0 <=1.2.3 true +gtr 1.0.0 <=1.2.3 false +ltr 1.0.0 <=1.2.3 false +satisfies 1.0.0 ^1.2.3 false +gtr 1.0.0 ^1.2.3 false +ltr 1.0.0 ^1.2.3 true +satisfies 1.0.0 ~1.2.3 false +gtr 1.0.0 ~1.2.3 false +ltr 1.0.0 ~1.2.3 true +satisfies 1.0.0 ^0.2.3 false +gtr 1.0.0 ^0.2.3 true +ltr 1.0.0 ^0.2.3 false +satisfies 1.0.0 ^0.0.3 false +gtr 1.0.0 ^0.0.3 true +ltr 1.0.0 ^0.0.3 false +satisfies 1.0.0 1.2.x false +gtr 1.0.0 1.2.x false +ltr 1.0.0 1.2.x true +satisfies 1.0.0 1.x true +gtr 1.0.0 1.x false +ltr 1.0.0 1.x false +satisfies 1.0.0 * true +gtr 1.0.0 * false +ltr 1.0.0 * false +satisfies 1.0.0 >=1.2.3 <2.0.0 false +gtr 1.0.0 >=1.2.3 <2.0.0 false +ltr 1.0.0 >=1.2.3 <2.0.0 true +satisfies 1.0.0 1.2.3 - 2.3.4 false +gtr 1.0.0 1.2.3 - 2.3.4 false +ltr 1.0.0 1.2.3 - 2.3.4 true +satisfies 1.0.0 1.2.3 || >=2.0.0 false +gtr 1.0.0 1.2.3 || >=2.0.0 false +ltr 1.0.0 1.2.3 || >=2.0.0 true +satisfies 1.0.0 ^1.2.3-alpha false +gtr 1.0.0 ^1.2.3-alpha false +ltr 1.0.0 ^1.2.3-alpha true +satisfies 1.0.0 ~1.2 false +gtr 1.0.0 ~1.2 false +ltr 1.0.0 ~1.2 true +satisfies 1.0.0 ^1 true +gtr 1.0.0 ^1 false +ltr 1.0.0 ^1 false +satisfies 1.2.3 1.2.3 true +gtr 1.2.3 1.2.3 false +ltr 1.2.3 1.2.3 false +satisfies 1.2.3 =1.2.3 true +gtr 1.2.3 =1.2.3 false +ltr 1.2.3 =1.2.3 false +satisfies 1.2.3 >1.2.3 false +gtr 1.2.3 >1.2.3 false +ltr 1.2.3 >1.2.3 true +satisfies 1.2.3 >=1.2.3 true +gtr 1.2.3 >=1.2.3 false +ltr 1.2.3 >=1.2.3 false +satisfies 1.2.3 <1.2.3 false +gtr 1.2.3 <1.2.3 true +ltr 1.2.3 <1.2.3 false +satisfies 1.2.3 <=1.2.3 true +gtr 1.2.3 <=1.2.3 false +ltr 1.2.3 <=1.2.3 false +satisfies 1.2.3 ^1.2.3 true +gtr 1.2.3 ^1.2.3 false +ltr 1.2.3 ^1.2.3 false +satisfies 1.2.3 ~1.2.3 true +gtr 1.2.3 ~1.2.3 false +ltr 1.2.3 ~1.2.3 false +satisfies 1.2.3 ^0.2.3 false +gtr 1.2.3 ^0.2.3 true +ltr 1.2.3 ^0.2.3 false +satisfies 1.2.3 ^0.0.3 false +gtr 1.2.3 ^0.0.3 true +ltr 1.2.3 ^0.0.3 false +satisfies 1.2.3 1.2.x true +gtr 1.2.3 1.2.x false +ltr 1.2.3 1.2.x false +satisfies 1.2.3 1.x true +gtr 1.2.3 1.x false +ltr 1.2.3 1.x false +satisfies 1.2.3 * true +gtr 1.2.3 * false +ltr 1.2.3 * false +satisfies 1.2.3 >=1.2.3 <2.0.0 true +gtr 1.2.3 >=1.2.3 <2.0.0 false +ltr 1.2.3 >=1.2.3 <2.0.0 false +satisfies 1.2.3 1.2.3 - 2.3.4 true +gtr 1.2.3 1.2.3 - 2.3.4 false +ltr 1.2.3 1.2.3 - 2.3.4 false +satisfies 1.2.3 1.2.3 || >=2.0.0 true +gtr 1.2.3 1.2.3 || >=2.0.0 false +ltr 1.2.3 1.2.3 || >=2.0.0 false +satisfies 1.2.3 ^1.2.3-alpha true +gtr 1.2.3 ^1.2.3-alpha false +ltr 1.2.3 ^1.2.3-alpha false +satisfies 1.2.3 ~1.2 true +gtr 1.2.3 ~1.2 false +ltr 1.2.3 ~1.2 false +satisfies 1.2.3 ^1 true +gtr 1.2.3 ^1 false +ltr 1.2.3 ^1 false +satisfies 1.2.4 1.2.3 false +gtr 1.2.4 1.2.3 true +ltr 1.2.4 1.2.3 false +satisfies 1.2.4 =1.2.3 false +gtr 1.2.4 =1.2.3 true +ltr 1.2.4 =1.2.3 false +satisfies 1.2.4 >1.2.3 true +gtr 1.2.4 >1.2.3 false +ltr 1.2.4 >1.2.3 false +satisfies 1.2.4 >=1.2.3 true +gtr 1.2.4 >=1.2.3 false +ltr 1.2.4 >=1.2.3 false +satisfies 1.2.4 <1.2.3 false +gtr 1.2.4 <1.2.3 true +ltr 1.2.4 <1.2.3 false +satisfies 1.2.4 <=1.2.3 false +gtr 1.2.4 <=1.2.3 true +ltr 1.2.4 <=1.2.3 false +satisfies 1.2.4 ^1.2.3 true +gtr 1.2.4 ^1.2.3 false +ltr 1.2.4 ^1.2.3 false +satisfies 1.2.4 ~1.2.3 true +gtr 1.2.4 ~1.2.3 false +ltr 1.2.4 ~1.2.3 false +satisfies 1.2.4 ^0.2.3 false +gtr 1.2.4 ^0.2.3 true +ltr 1.2.4 ^0.2.3 false +satisfies 1.2.4 ^0.0.3 false +gtr 1.2.4 ^0.0.3 true +ltr 1.2.4 ^0.0.3 false +satisfies 1.2.4 1.2.x true +gtr 1.2.4 1.2.x false +ltr 1.2.4 1.2.x false +satisfies 1.2.4 1.x true +gtr 1.2.4 1.x false +ltr 1.2.4 1.x false +satisfies 1.2.4 * true +gtr 1.2.4 * false +ltr 1.2.4 * false +satisfies 1.2.4 >=1.2.3 <2.0.0 true +gtr 1.2.4 >=1.2.3 <2.0.0 false +ltr 1.2.4 >=1.2.3 <2.0.0 false +satisfies 1.2.4 1.2.3 - 2.3.4 true +gtr 1.2.4 1.2.3 - 2.3.4 false +ltr 1.2.4 1.2.3 - 2.3.4 false +satisfies 1.2.4 1.2.3 || >=2.0.0 false +gtr 1.2.4 1.2.3 || >=2.0.0 false +ltr 1.2.4 1.2.3 || >=2.0.0 false +satisfies 1.2.4 ^1.2.3-alpha true +gtr 1.2.4 ^1.2.3-alpha false +ltr 1.2.4 ^1.2.3-alpha false +satisfies 1.2.4 ~1.2 true +gtr 1.2.4 ~1.2 false +ltr 1.2.4 ~1.2 false +satisfies 1.2.4 ^1 true +gtr 1.2.4 ^1 false +ltr 1.2.4 ^1 false +satisfies 1.3.0 1.2.3 false +gtr 1.3.0 1.2.3 true +ltr 1.3.0 1.2.3 false +satisfies 1.3.0 =1.2.3 false +gtr 1.3.0 =1.2.3 true +ltr 1.3.0 =1.2.3 false +satisfies 1.3.0 >1.2.3 true +gtr 1.3.0 >1.2.3 false +ltr 1.3.0 >1.2.3 false +satisfies 1.3.0 >=1.2.3 true +gtr 1.3.0 >=1.2.3 false +ltr 1.3.0 >=1.2.3 false +satisfies 1.3.0 <1.2.3 false +gtr 1.3.0 <1.2.3 true +ltr 1.3.0 <1.2.3 false +satisfies 1.3.0 <=1.2.3 false +gtr 1.3.0 <=1.2.3 true +ltr 1.3.0 <=1.2.3 false +satisfies 1.3.0 ^1.2.3 true +gtr 1.3.0 ^1.2.3 false +ltr 1.3.0 ^1.2.3 false +satisfies 1.3.0 ~1.2.3 false +gtr 1.3.0 ~1.2.3 true +ltr 1.3.0 ~1.2.3 false +satisfies 1.3.0 ^0.2.3 false +gtr 1.3.0 ^0.2.3 true +ltr 1.3.0 ^0.2.3 false +satisfies 1.3.0 ^0.0.3 false +gtr 1.3.0 ^0.0.3 true +ltr 1.3.0 ^0.0.3 false +satisfies 1.3.0 1.2.x false +gtr 1.3.0 1.2.x true +ltr 1.3.0 1.2.x false +satisfies 1.3.0 1.x true +gtr 1.3.0 1.x false +ltr 1.3.0 1.x false +satisfies 1.3.0 * true +gtr 1.3.0 * false +ltr 1.3.0 * false +satisfies 1.3.0 >=1.2.3 <2.0.0 true +gtr 1.3.0 >=1.2.3 <2.0.0 false +ltr 1.3.0 >=1.2.3 <2.0.0 false +satisfies 1.3.0 1.2.3 - 2.3.4 true +gtr 1.3.0 1.2.3 - 2.3.4 false +ltr 1.3.0 1.2.3 - 2.3.4 false +satisfies 1.3.0 1.2.3 || >=2.0.0 false +gtr 1.3.0 1.2.3 || >=2.0.0 false +ltr 1.3.0 1.2.3 || >=2.0.0 false +satisfies 1.3.0 ^1.2.3-alpha true +gtr 1.3.0 ^1.2.3-alpha false +ltr 1.3.0 ^1.2.3-alpha false +satisfies 1.3.0 ~1.2 false +gtr 1.3.0 ~1.2 true +ltr 1.3.0 ~1.2 false +satisfies 1.3.0 ^1 true +gtr 1.3.0 ^1 false +ltr 1.3.0 ^1 false +satisfies 2.0.0 1.2.3 false +gtr 2.0.0 1.2.3 true +ltr 2.0.0 1.2.3 false +satisfies 2.0.0 =1.2.3 false +gtr 2.0.0 =1.2.3 true +ltr 2.0.0 =1.2.3 false +satisfies 2.0.0 >1.2.3 true +gtr 2.0.0 >1.2.3 false +ltr 2.0.0 >1.2.3 false +satisfies 2.0.0 >=1.2.3 true +gtr 2.0.0 >=1.2.3 false +ltr 2.0.0 >=1.2.3 false +satisfies 2.0.0 <1.2.3 false +gtr 2.0.0 <1.2.3 true +ltr 2.0.0 <1.2.3 false +satisfies 2.0.0 <=1.2.3 false +gtr 2.0.0 <=1.2.3 true +ltr 2.0.0 <=1.2.3 false +satisfies 2.0.0 ^1.2.3 false +gtr 2.0.0 ^1.2.3 true +ltr 2.0.0 ^1.2.3 false +satisfies 2.0.0 ~1.2.3 false +gtr 2.0.0 ~1.2.3 true +ltr 2.0.0 ~1.2.3 false +satisfies 2.0.0 ^0.2.3 false +gtr 2.0.0 ^0.2.3 true +ltr 2.0.0 ^0.2.3 false +satisfies 2.0.0 ^0.0.3 false +gtr 2.0.0 ^0.0.3 true +ltr 2.0.0 ^0.0.3 false +satisfies 2.0.0 1.2.x false +gtr 2.0.0 1.2.x true +ltr 2.0.0 1.2.x false +satisfies 2.0.0 1.x false +gtr 2.0.0 1.x true +ltr 2.0.0 1.x false +satisfies 2.0.0 * true +gtr 2.0.0 * false +ltr 2.0.0 * false +satisfies 2.0.0 >=1.2.3 <2.0.0 false +gtr 2.0.0 >=1.2.3 <2.0.0 true +ltr 2.0.0 >=1.2.3 <2.0.0 false +satisfies 2.0.0 1.2.3 - 2.3.4 true +gtr 2.0.0 1.2.3 - 2.3.4 false +ltr 2.0.0 1.2.3 - 2.3.4 false +satisfies 2.0.0 1.2.3 || >=2.0.0 true +gtr 2.0.0 1.2.3 || >=2.0.0 false +ltr 2.0.0 1.2.3 || >=2.0.0 false +satisfies 2.0.0 ^1.2.3-alpha false +gtr 2.0.0 ^1.2.3-alpha true +ltr 2.0.0 ^1.2.3-alpha false +satisfies 2.0.0 ~1.2 false +gtr 2.0.0 ~1.2 true +ltr 2.0.0 ~1.2 false +satisfies 2.0.0 ^1 false +gtr 2.0.0 ^1 true +ltr 2.0.0 ^1 false +satisfies 1.0.0-alpha 1.2.3 false +gtr 1.0.0-alpha 1.2.3 false +ltr 1.0.0-alpha 1.2.3 true +satisfies 1.0.0-alpha =1.2.3 false +gtr 1.0.0-alpha =1.2.3 false +ltr 1.0.0-alpha =1.2.3 true +satisfies 1.0.0-alpha >1.2.3 false +gtr 1.0.0-alpha >1.2.3 false +ltr 1.0.0-alpha >1.2.3 true +satisfies 1.0.0-alpha >=1.2.3 false +gtr 1.0.0-alpha >=1.2.3 false +ltr 1.0.0-alpha >=1.2.3 true +satisfies 1.0.0-alpha <1.2.3 false +gtr 1.0.0-alpha <1.2.3 true +ltr 1.0.0-alpha <1.2.3 false +satisfies 1.0.0-alpha <=1.2.3 false +gtr 1.0.0-alpha <=1.2.3 true +ltr 1.0.0-alpha <=1.2.3 false +satisfies 1.0.0-alpha ^1.2.3 false +gtr 1.0.0-alpha ^1.2.3 false +ltr 1.0.0-alpha ^1.2.3 true +satisfies 1.0.0-alpha ~1.2.3 false +gtr 1.0.0-alpha ~1.2.3 false +ltr 1.0.0-alpha ~1.2.3 true +satisfies 1.0.0-alpha ^0.2.3 false +gtr 1.0.0-alpha ^0.2.3 true +ltr 1.0.0-alpha ^0.2.3 false +satisfies 1.0.0-alpha ^0.0.3 false +gtr 1.0.0-alpha ^0.0.3 true +ltr 1.0.0-alpha ^0.0.3 false +satisfies 1.0.0-alpha 1.2.x false +gtr 1.0.0-alpha 1.2.x false +ltr 1.0.0-alpha 1.2.x true +satisfies 1.0.0-alpha 1.x false +gtr 1.0.0-alpha 1.x false +ltr 1.0.0-alpha 1.x true +satisfies 1.0.0-alpha * false +gtr 1.0.0-alpha * false +ltr 1.0.0-alpha * true +satisfies 1.0.0-alpha >=1.2.3 <2.0.0 false +gtr 1.0.0-alpha >=1.2.3 <2.0.0 false +ltr 1.0.0-alpha >=1.2.3 <2.0.0 true +satisfies 1.0.0-alpha 1.2.3 - 2.3.4 false +gtr 1.0.0-alpha 1.2.3 - 2.3.4 false +ltr 1.0.0-alpha 1.2.3 - 2.3.4 true +satisfies 1.0.0-alpha 1.2.3 || >=2.0.0 false +gtr 1.0.0-alpha 1.2.3 || >=2.0.0 false +ltr 1.0.0-alpha 1.2.3 || >=2.0.0 true +satisfies 1.0.0-alpha ^1.2.3-alpha false +gtr 1.0.0-alpha ^1.2.3-alpha false +ltr 1.0.0-alpha ^1.2.3-alpha true +satisfies 1.0.0-alpha ~1.2 false +gtr 1.0.0-alpha ~1.2 false +ltr 1.0.0-alpha ~1.2 true +satisfies 1.0.0-alpha ^1 false +gtr 1.0.0-alpha ^1 false +ltr 1.0.0-alpha ^1 true +satisfies 1.0.0-alpha.1 1.2.3 false +gtr 1.0.0-alpha.1 1.2.3 false +ltr 1.0.0-alpha.1 1.2.3 true +satisfies 1.0.0-alpha.1 =1.2.3 false +gtr 1.0.0-alpha.1 =1.2.3 false +ltr 1.0.0-alpha.1 =1.2.3 true +satisfies 1.0.0-alpha.1 >1.2.3 false +gtr 1.0.0-alpha.1 >1.2.3 false +ltr 1.0.0-alpha.1 >1.2.3 true +satisfies 1.0.0-alpha.1 >=1.2.3 false +gtr 1.0.0-alpha.1 >=1.2.3 false +ltr 1.0.0-alpha.1 >=1.2.3 true +satisfies 1.0.0-alpha.1 <1.2.3 false +gtr 1.0.0-alpha.1 <1.2.3 true +ltr 1.0.0-alpha.1 <1.2.3 false +satisfies 1.0.0-alpha.1 <=1.2.3 false +gtr 1.0.0-alpha.1 <=1.2.3 true +ltr 1.0.0-alpha.1 <=1.2.3 false +satisfies 1.0.0-alpha.1 ^1.2.3 false +gtr 1.0.0-alpha.1 ^1.2.3 false +ltr 1.0.0-alpha.1 ^1.2.3 true +satisfies 1.0.0-alpha.1 ~1.2.3 false +gtr 1.0.0-alpha.1 ~1.2.3 false +ltr 1.0.0-alpha.1 ~1.2.3 true +satisfies 1.0.0-alpha.1 ^0.2.3 false +gtr 1.0.0-alpha.1 ^0.2.3 true +ltr 1.0.0-alpha.1 ^0.2.3 false +satisfies 1.0.0-alpha.1 ^0.0.3 false +gtr 1.0.0-alpha.1 ^0.0.3 true +ltr 1.0.0-alpha.1 ^0.0.3 false +satisfies 1.0.0-alpha.1 1.2.x false +gtr 1.0.0-alpha.1 1.2.x false +ltr 1.0.0-alpha.1 1.2.x true +satisfies 1.0.0-alpha.1 1.x false +gtr 1.0.0-alpha.1 1.x false +ltr 1.0.0-alpha.1 1.x true +satisfies 1.0.0-alpha.1 * false +gtr 1.0.0-alpha.1 * false +ltr 1.0.0-alpha.1 * true +satisfies 1.0.0-alpha.1 >=1.2.3 <2.0.0 false +gtr 1.0.0-alpha.1 >=1.2.3 <2.0.0 false +ltr 1.0.0-alpha.1 >=1.2.3 <2.0.0 true +satisfies 1.0.0-alpha.1 1.2.3 - 2.3.4 false +gtr 1.0.0-alpha.1 1.2.3 - 2.3.4 false +ltr 1.0.0-alpha.1 1.2.3 - 2.3.4 true +satisfies 1.0.0-alpha.1 1.2.3 || >=2.0.0 false +gtr 1.0.0-alpha.1 1.2.3 || >=2.0.0 false +ltr 1.0.0-alpha.1 1.2.3 || >=2.0.0 true +satisfies 1.0.0-alpha.1 ^1.2.3-alpha false +gtr 1.0.0-alpha.1 ^1.2.3-alpha false +ltr 1.0.0-alpha.1 ^1.2.3-alpha true +satisfies 1.0.0-alpha.1 ~1.2 false +gtr 1.0.0-alpha.1 ~1.2 false +ltr 1.0.0-alpha.1 ~1.2 true +satisfies 1.0.0-alpha.1 ^1 false +gtr 1.0.0-alpha.1 ^1 false +ltr 1.0.0-alpha.1 ^1 true +satisfies 1.0.0-alpha.beta 1.2.3 false +gtr 1.0.0-alpha.beta 1.2.3 false +ltr 1.0.0-alpha.beta 1.2.3 true +satisfies 1.0.0-alpha.beta =1.2.3 false +gtr 1.0.0-alpha.beta =1.2.3 false +ltr 1.0.0-alpha.beta =1.2.3 true +satisfies 1.0.0-alpha.beta >1.2.3 false +gtr 1.0.0-alpha.beta >1.2.3 false +ltr 1.0.0-alpha.beta >1.2.3 true +satisfies 1.0.0-alpha.beta >=1.2.3 false +gtr 1.0.0-alpha.beta >=1.2.3 false +ltr 1.0.0-alpha.beta >=1.2.3 true +satisfies 1.0.0-alpha.beta <1.2.3 false +gtr 1.0.0-alpha.beta <1.2.3 true +ltr 1.0.0-alpha.beta <1.2.3 false +satisfies 1.0.0-alpha.beta <=1.2.3 false +gtr 1.0.0-alpha.beta <=1.2.3 true +ltr 1.0.0-alpha.beta <=1.2.3 false +satisfies 1.0.0-alpha.beta ^1.2.3 false +gtr 1.0.0-alpha.beta ^1.2.3 false +ltr 1.0.0-alpha.beta ^1.2.3 true +satisfies 1.0.0-alpha.beta ~1.2.3 false +gtr 1.0.0-alpha.beta ~1.2.3 false +ltr 1.0.0-alpha.beta ~1.2.3 true +satisfies 1.0.0-alpha.beta ^0.2.3 false +gtr 1.0.0-alpha.beta ^0.2.3 true +ltr 1.0.0-alpha.beta ^0.2.3 false +satisfies 1.0.0-alpha.beta ^0.0.3 false +gtr 1.0.0-alpha.beta ^0.0.3 true +ltr 1.0.0-alpha.beta ^0.0.3 false +satisfies 1.0.0-alpha.beta 1.2.x false +gtr 1.0.0-alpha.beta 1.2.x false +ltr 1.0.0-alpha.beta 1.2.x true +satisfies 1.0.0-alpha.beta 1.x false +gtr 1.0.0-alpha.beta 1.x false +ltr 1.0.0-alpha.beta 1.x true +satisfies 1.0.0-alpha.beta * false +gtr 1.0.0-alpha.beta * false +ltr 1.0.0-alpha.beta * true +satisfies 1.0.0-alpha.beta >=1.2.3 <2.0.0 false +gtr 1.0.0-alpha.beta >=1.2.3 <2.0.0 false +ltr 1.0.0-alpha.beta >=1.2.3 <2.0.0 true +satisfies 1.0.0-alpha.beta 1.2.3 - 2.3.4 false +gtr 1.0.0-alpha.beta 1.2.3 - 2.3.4 false +ltr 1.0.0-alpha.beta 1.2.3 - 2.3.4 true +satisfies 1.0.0-alpha.beta 1.2.3 || >=2.0.0 false +gtr 1.0.0-alpha.beta 1.2.3 || >=2.0.0 false +ltr 1.0.0-alpha.beta 1.2.3 || >=2.0.0 true +satisfies 1.0.0-alpha.beta ^1.2.3-alpha false +gtr 1.0.0-alpha.beta ^1.2.3-alpha false +ltr 1.0.0-alpha.beta ^1.2.3-alpha true +satisfies 1.0.0-alpha.beta ~1.2 false +gtr 1.0.0-alpha.beta ~1.2 false +ltr 1.0.0-alpha.beta ~1.2 true +satisfies 1.0.0-alpha.beta ^1 false +gtr 1.0.0-alpha.beta ^1 false +ltr 1.0.0-alpha.beta ^1 true +satisfies 1.0.0-beta 1.2.3 false +gtr 1.0.0-beta 1.2.3 false +ltr 1.0.0-beta 1.2.3 true +satisfies 1.0.0-beta =1.2.3 false +gtr 1.0.0-beta =1.2.3 false +ltr 1.0.0-beta =1.2.3 true +satisfies 1.0.0-beta >1.2.3 false +gtr 1.0.0-beta >1.2.3 false +ltr 1.0.0-beta >1.2.3 true +satisfies 1.0.0-beta >=1.2.3 false +gtr 1.0.0-beta >=1.2.3 false +ltr 1.0.0-beta >=1.2.3 true +satisfies 1.0.0-beta <1.2.3 false +gtr 1.0.0-beta <1.2.3 true +ltr 1.0.0-beta <1.2.3 false +satisfies 1.0.0-beta <=1.2.3 false +gtr 1.0.0-beta <=1.2.3 true +ltr 1.0.0-beta <=1.2.3 false +satisfies 1.0.0-beta ^1.2.3 false +gtr 1.0.0-beta ^1.2.3 false +ltr 1.0.0-beta ^1.2.3 true +satisfies 1.0.0-beta ~1.2.3 false +gtr 1.0.0-beta ~1.2.3 false +ltr 1.0.0-beta ~1.2.3 true +satisfies 1.0.0-beta ^0.2.3 false +gtr 1.0.0-beta ^0.2.3 true +ltr 1.0.0-beta ^0.2.3 false +satisfies 1.0.0-beta ^0.0.3 false +gtr 1.0.0-beta ^0.0.3 true +ltr 1.0.0-beta ^0.0.3 false +satisfies 1.0.0-beta 1.2.x false +gtr 1.0.0-beta 1.2.x false +ltr 1.0.0-beta 1.2.x true +satisfies 1.0.0-beta 1.x false +gtr 1.0.0-beta 1.x false +ltr 1.0.0-beta 1.x true +satisfies 1.0.0-beta * false +gtr 1.0.0-beta * false +ltr 1.0.0-beta * true +satisfies 1.0.0-beta >=1.2.3 <2.0.0 false +gtr 1.0.0-beta >=1.2.3 <2.0.0 false +ltr 1.0.0-beta >=1.2.3 <2.0.0 true +satisfies 1.0.0-beta 1.2.3 - 2.3.4 false +gtr 1.0.0-beta 1.2.3 - 2.3.4 false +ltr 1.0.0-beta 1.2.3 - 2.3.4 true +satisfies 1.0.0-beta 1.2.3 || >=2.0.0 false +gtr 1.0.0-beta 1.2.3 || >=2.0.0 false +ltr 1.0.0-beta 1.2.3 || >=2.0.0 true +satisfies 1.0.0-beta ^1.2.3-alpha false +gtr 1.0.0-beta ^1.2.3-alpha false +ltr 1.0.0-beta ^1.2.3-alpha true +satisfies 1.0.0-beta ~1.2 false +gtr 1.0.0-beta ~1.2 false +ltr 1.0.0-beta ~1.2 true +satisfies 1.0.0-beta ^1 false +gtr 1.0.0-beta ^1 false +ltr 1.0.0-beta ^1 true +satisfies 1.0.0-beta.2 1.2.3 false +gtr 1.0.0-beta.2 1.2.3 false +ltr 1.0.0-beta.2 1.2.3 true +satisfies 1.0.0-beta.2 =1.2.3 false +gtr 1.0.0-beta.2 =1.2.3 false +ltr 1.0.0-beta.2 =1.2.3 true +satisfies 1.0.0-beta.2 >1.2.3 false +gtr 1.0.0-beta.2 >1.2.3 false +ltr 1.0.0-beta.2 >1.2.3 true +satisfies 1.0.0-beta.2 >=1.2.3 false +gtr 1.0.0-beta.2 >=1.2.3 false +ltr 1.0.0-beta.2 >=1.2.3 true +satisfies 1.0.0-beta.2 <1.2.3 false +gtr 1.0.0-beta.2 <1.2.3 true +ltr 1.0.0-beta.2 <1.2.3 false +satisfies 1.0.0-beta.2 <=1.2.3 false +gtr 1.0.0-beta.2 <=1.2.3 true +ltr 1.0.0-beta.2 <=1.2.3 false +satisfies 1.0.0-beta.2 ^1.2.3 false +gtr 1.0.0-beta.2 ^1.2.3 false +ltr 1.0.0-beta.2 ^1.2.3 true +satisfies 1.0.0-beta.2 ~1.2.3 false +gtr 1.0.0-beta.2 ~1.2.3 false +ltr 1.0.0-beta.2 ~1.2.3 true +satisfies 1.0.0-beta.2 ^0.2.3 false +gtr 1.0.0-beta.2 ^0.2.3 true +ltr 1.0.0-beta.2 ^0.2.3 false +satisfies 1.0.0-beta.2 ^0.0.3 false +gtr 1.0.0-beta.2 ^0.0.3 true +ltr 1.0.0-beta.2 ^0.0.3 false +satisfies 1.0.0-beta.2 1.2.x false +gtr 1.0.0-beta.2 1.2.x false +ltr 1.0.0-beta.2 1.2.x true +satisfies 1.0.0-beta.2 1.x false +gtr 1.0.0-beta.2 1.x false +ltr 1.0.0-beta.2 1.x true +satisfies 1.0.0-beta.2 * false +gtr 1.0.0-beta.2 * false +ltr 1.0.0-beta.2 * true +satisfies 1.0.0-beta.2 >=1.2.3 <2.0.0 false +gtr 1.0.0-beta.2 >=1.2.3 <2.0.0 false +ltr 1.0.0-beta.2 >=1.2.3 <2.0.0 true +satisfies 1.0.0-beta.2 1.2.3 - 2.3.4 false +gtr 1.0.0-beta.2 1.2.3 - 2.3.4 false +ltr 1.0.0-beta.2 1.2.3 - 2.3.4 true +satisfies 1.0.0-beta.2 1.2.3 || >=2.0.0 false +gtr 1.0.0-beta.2 1.2.3 || >=2.0.0 false +ltr 1.0.0-beta.2 1.2.3 || >=2.0.0 true +satisfies 1.0.0-beta.2 ^1.2.3-alpha false +gtr 1.0.0-beta.2 ^1.2.3-alpha false +ltr 1.0.0-beta.2 ^1.2.3-alpha true +satisfies 1.0.0-beta.2 ~1.2 false +gtr 1.0.0-beta.2 ~1.2 false +ltr 1.0.0-beta.2 ~1.2 true +satisfies 1.0.0-beta.2 ^1 false +gtr 1.0.0-beta.2 ^1 false +ltr 1.0.0-beta.2 ^1 true +satisfies 1.0.0-beta.11 1.2.3 false +gtr 1.0.0-beta.11 1.2.3 false +ltr 1.0.0-beta.11 1.2.3 true +satisfies 1.0.0-beta.11 =1.2.3 false +gtr 1.0.0-beta.11 =1.2.3 false +ltr 1.0.0-beta.11 =1.2.3 true +satisfies 1.0.0-beta.11 >1.2.3 false +gtr 1.0.0-beta.11 >1.2.3 false +ltr 1.0.0-beta.11 >1.2.3 true +satisfies 1.0.0-beta.11 >=1.2.3 false +gtr 1.0.0-beta.11 >=1.2.3 false +ltr 1.0.0-beta.11 >=1.2.3 true +satisfies 1.0.0-beta.11 <1.2.3 false +gtr 1.0.0-beta.11 <1.2.3 true +ltr 1.0.0-beta.11 <1.2.3 false +satisfies 1.0.0-beta.11 <=1.2.3 false +gtr 1.0.0-beta.11 <=1.2.3 true +ltr 1.0.0-beta.11 <=1.2.3 false +satisfies 1.0.0-beta.11 ^1.2.3 false +gtr 1.0.0-beta.11 ^1.2.3 false +ltr 1.0.0-beta.11 ^1.2.3 true +satisfies 1.0.0-beta.11 ~1.2.3 false +gtr 1.0.0-beta.11 ~1.2.3 false +ltr 1.0.0-beta.11 ~1.2.3 true +satisfies 1.0.0-beta.11 ^0.2.3 false +gtr 1.0.0-beta.11 ^0.2.3 true +ltr 1.0.0-beta.11 ^0.2.3 false +satisfies 1.0.0-beta.11 ^0.0.3 false +gtr 1.0.0-beta.11 ^0.0.3 true +ltr 1.0.0-beta.11 ^0.0.3 false +satisfies 1.0.0-beta.11 1.2.x false +gtr 1.0.0-beta.11 1.2.x false +ltr 1.0.0-beta.11 1.2.x true +satisfies 1.0.0-beta.11 1.x false +gtr 1.0.0-beta.11 1.x false +ltr 1.0.0-beta.11 1.x true +satisfies 1.0.0-beta.11 * false +gtr 1.0.0-beta.11 * false +ltr 1.0.0-beta.11 * true +satisfies 1.0.0-beta.11 >=1.2.3 <2.0.0 false +gtr 1.0.0-beta.11 >=1.2.3 <2.0.0 false +ltr 1.0.0-beta.11 >=1.2.3 <2.0.0 true +satisfies 1.0.0-beta.11 1.2.3 - 2.3.4 false +gtr 1.0.0-beta.11 1.2.3 - 2.3.4 false +ltr 1.0.0-beta.11 1.2.3 - 2.3.4 true +satisfies 1.0.0-beta.11 1.2.3 || >=2.0.0 false +gtr 1.0.0-beta.11 1.2.3 || >=2.0.0 false +ltr 1.0.0-beta.11 1.2.3 || >=2.0.0 true +satisfies 1.0.0-beta.11 ^1.2.3-alpha false +gtr 1.0.0-beta.11 ^1.2.3-alpha false +ltr 1.0.0-beta.11 ^1.2.3-alpha true +satisfies 1.0.0-beta.11 ~1.2 false +gtr 1.0.0-beta.11 ~1.2 false +ltr 1.0.0-beta.11 ~1.2 true +satisfies 1.0.0-beta.11 ^1 false +gtr 1.0.0-beta.11 ^1 false +ltr 1.0.0-beta.11 ^1 true +satisfies 1.0.0-rc.1 1.2.3 false +gtr 1.0.0-rc.1 1.2.3 false +ltr 1.0.0-rc.1 1.2.3 true +satisfies 1.0.0-rc.1 =1.2.3 false +gtr 1.0.0-rc.1 =1.2.3 false +ltr 1.0.0-rc.1 =1.2.3 true +satisfies 1.0.0-rc.1 >1.2.3 false +gtr 1.0.0-rc.1 >1.2.3 false +ltr 1.0.0-rc.1 >1.2.3 true +satisfies 1.0.0-rc.1 >=1.2.3 false +gtr 1.0.0-rc.1 >=1.2.3 false +ltr 1.0.0-rc.1 >=1.2.3 true +satisfies 1.0.0-rc.1 <1.2.3 false +gtr 1.0.0-rc.1 <1.2.3 true +ltr 1.0.0-rc.1 <1.2.3 false +satisfies 1.0.0-rc.1 <=1.2.3 false +gtr 1.0.0-rc.1 <=1.2.3 true +ltr 1.0.0-rc.1 <=1.2.3 false +satisfies 1.0.0-rc.1 ^1.2.3 false +gtr 1.0.0-rc.1 ^1.2.3 false +ltr 1.0.0-rc.1 ^1.2.3 true +satisfies 1.0.0-rc.1 ~1.2.3 false +gtr 1.0.0-rc.1 ~1.2.3 false +ltr 1.0.0-rc.1 ~1.2.3 true +satisfies 1.0.0-rc.1 ^0.2.3 false +gtr 1.0.0-rc.1 ^0.2.3 true +ltr 1.0.0-rc.1 ^0.2.3 false +satisfies 1.0.0-rc.1 ^0.0.3 false +gtr 1.0.0-rc.1 ^0.0.3 true +ltr 1.0.0-rc.1 ^0.0.3 false +satisfies 1.0.0-rc.1 1.2.x false +gtr 1.0.0-rc.1 1.2.x false +ltr 1.0.0-rc.1 1.2.x true +satisfies 1.0.0-rc.1 1.x false +gtr 1.0.0-rc.1 1.x false +ltr 1.0.0-rc.1 1.x true +satisfies 1.0.0-rc.1 * false +gtr 1.0.0-rc.1 * false +ltr 1.0.0-rc.1 * true +satisfies 1.0.0-rc.1 >=1.2.3 <2.0.0 false +gtr 1.0.0-rc.1 >=1.2.3 <2.0.0 false +ltr 1.0.0-rc.1 >=1.2.3 <2.0.0 true +satisfies 1.0.0-rc.1 1.2.3 - 2.3.4 false +gtr 1.0.0-rc.1 1.2.3 - 2.3.4 false +ltr 1.0.0-rc.1 1.2.3 - 2.3.4 true +satisfies 1.0.0-rc.1 1.2.3 || >=2.0.0 false +gtr 1.0.0-rc.1 1.2.3 || >=2.0.0 false +ltr 1.0.0-rc.1 1.2.3 || >=2.0.0 true +satisfies 1.0.0-rc.1 ^1.2.3-alpha false +gtr 1.0.0-rc.1 ^1.2.3-alpha false +ltr 1.0.0-rc.1 ^1.2.3-alpha true +satisfies 1.0.0-rc.1 ~1.2 false +gtr 1.0.0-rc.1 ~1.2 false +ltr 1.0.0-rc.1 ~1.2 true +satisfies 1.0.0-rc.1 ^1 false +gtr 1.0.0-rc.1 ^1 false +ltr 1.0.0-rc.1 ^1 true +satisfies 1.2.3-0 1.2.3 false +gtr 1.2.3-0 1.2.3 false +ltr 1.2.3-0 1.2.3 true +satisfies 1.2.3-0 =1.2.3 false +gtr 1.2.3-0 =1.2.3 false +ltr 1.2.3-0 =1.2.3 true +satisfies 1.2.3-0 >1.2.3 false +gtr 1.2.3-0 >1.2.3 false +ltr 1.2.3-0 >1.2.3 true +satisfies 1.2.3-0 >=1.2.3 false +gtr 1.2.3-0 >=1.2.3 false +ltr 1.2.3-0 >=1.2.3 true +satisfies 1.2.3-0 <1.2.3 false +gtr 1.2.3-0 <1.2.3 true +ltr 1.2.3-0 <1.2.3 false +satisfies 1.2.3-0 <=1.2.3 false +gtr 1.2.3-0 <=1.2.3 true +ltr 1.2.3-0 <=1.2.3 false +satisfies 1.2.3-0 ^1.2.3 false +gtr 1.2.3-0 ^1.2.3 false +ltr 1.2.3-0 ^1.2.3 true +satisfies 1.2.3-0 ~1.2.3 false +gtr 1.2.3-0 ~1.2.3 false +ltr 1.2.3-0 ~1.2.3 true +satisfies 1.2.3-0 ^0.2.3 false +gtr 1.2.3-0 ^0.2.3 true +ltr 1.2.3-0 ^0.2.3 false +satisfies 1.2.3-0 ^0.0.3 false +gtr 1.2.3-0 ^0.0.3 true +ltr 1.2.3-0 ^0.0.3 false +satisfies 1.2.3-0 1.2.x false +gtr 1.2.3-0 1.2.x true +ltr 1.2.3-0 1.2.x true +satisfies 1.2.3-0 1.x false +gtr 1.2.3-0 1.x true +ltr 1.2.3-0 1.x true +satisfies 1.2.3-0 * false +gtr 1.2.3-0 * false +ltr 1.2.3-0 * true +satisfies 1.2.3-0 >=1.2.3 <2.0.0 false +gtr 1.2.3-0 >=1.2.3 <2.0.0 false +ltr 1.2.3-0 >=1.2.3 <2.0.0 true +satisfies 1.2.3-0 1.2.3 - 2.3.4 false +gtr 1.2.3-0 1.2.3 - 2.3.4 false +ltr 1.2.3-0 1.2.3 - 2.3.4 true +satisfies 1.2.3-0 1.2.3 || >=2.0.0 false +gtr 1.2.3-0 1.2.3 || >=2.0.0 false +ltr 1.2.3-0 1.2.3 || >=2.0.0 true +satisfies 1.2.3-0 ^1.2.3-alpha false +gtr 1.2.3-0 ^1.2.3-alpha false +ltr 1.2.3-0 ^1.2.3-alpha true +satisfies 1.2.3-0 ~1.2 false +gtr 1.2.3-0 ~1.2 true +ltr 1.2.3-0 ~1.2 true +satisfies 1.2.3-0 ^1 false +gtr 1.2.3-0 ^1 true +ltr 1.2.3-0 ^1 true +satisfies 2.0.0-0 1.2.3 false +gtr 2.0.0-0 1.2.3 true +ltr 2.0.0-0 1.2.3 false +satisfies 2.0.0-0 =1.2.3 false +gtr 2.0.0-0 =1.2.3 true +ltr 2.0.0-0 =1.2.3 false +satisfies 2.0.0-0 >1.2.3 false +gtr 2.0.0-0 >1.2.3 false +ltr 2.0.0-0 >1.2.3 true +satisfies 2.0.0-0 >=1.2.3 false +gtr 2.0.0-0 >=1.2.3 false +ltr 2.0.0-0 >=1.2.3 true +satisfies 2.0.0-0 <1.2.3 false +gtr 2.0.0-0 <1.2.3 true +ltr 2.0.0-0 <1.2.3 false +satisfies 2.0.0-0 <=1.2.3 false +gtr 2.0.0-0 <=1.2.3 true +ltr 2.0.0-0 <=1.2.3 false +satisfies 2.0.0-0 ^1.2.3 false +gtr 2.0.0-0 ^1.2.3 true +ltr 2.0.0-0 ^1.2.3 false +satisfies 2.0.0-0 ~1.2.3 false +gtr 2.0.0-0 ~1.2.3 true +ltr 2.0.0-0 ~1.2.3 false +satisfies 2.0.0-0 ^0.2.3 false +gtr 2.0.0-0 ^0.2.3 true +ltr 2.0.0-0 ^0.2.3 false +satisfies 2.0.0-0 ^0.0.3 false +gtr 2.0.0-0 ^0.0.3 true +ltr 2.0.0-0 ^0.0.3 false +satisfies 2.0.0-0 1.2.x false +gtr 2.0.0-0 1.2.x true +ltr 2.0.0-0 1.2.x false +satisfies 2.0.0-0 1.x false +gtr 2.0.0-0 1.x true +ltr 2.0.0-0 1.x false +satisfies 2.0.0-0 * false +gtr 2.0.0-0 * false +ltr 2.0.0-0 * true +satisfies 2.0.0-0 >=1.2.3 <2.0.0 false +gtr 2.0.0-0 >=1.2.3 <2.0.0 true +ltr 2.0.0-0 >=1.2.3 <2.0.0 true +satisfies 2.0.0-0 1.2.3 - 2.3.4 false +gtr 2.0.0-0 1.2.3 - 2.3.4 true +ltr 2.0.0-0 1.2.3 - 2.3.4 true +satisfies 2.0.0-0 1.2.3 || >=2.0.0 false +gtr 2.0.0-0 1.2.3 || >=2.0.0 false +ltr 2.0.0-0 1.2.3 || >=2.0.0 false +satisfies 2.0.0-0 ^1.2.3-alpha false +gtr 2.0.0-0 ^1.2.3-alpha true +ltr 2.0.0-0 ^1.2.3-alpha false +satisfies 2.0.0-0 ~1.2 false +gtr 2.0.0-0 ~1.2 true +ltr 2.0.0-0 ~1.2 false +satisfies 2.0.0-0 ^1 false +gtr 2.0.0-0 ^1 true +ltr 2.0.0-0 ^1 false +satisfies 1.2.3+build 1.2.3 true +gtr 1.2.3+build 1.2.3 false +ltr 1.2.3+build 1.2.3 false +satisfies 1.2.3+build =1.2.3 true +gtr 1.2.3+build =1.2.3 false +ltr 1.2.3+build =1.2.3 false +satisfies 1.2.3+build >1.2.3 false +gtr 1.2.3+build >1.2.3 false +ltr 1.2.3+build >1.2.3 true +satisfies 1.2.3+build >=1.2.3 true +gtr 1.2.3+build >=1.2.3 false +ltr 1.2.3+build >=1.2.3 false +satisfies 1.2.3+build <1.2.3 false +gtr 1.2.3+build <1.2.3 true +ltr 1.2.3+build <1.2.3 false +satisfies 1.2.3+build <=1.2.3 true +gtr 1.2.3+build <=1.2.3 false +ltr 1.2.3+build <=1.2.3 false +satisfies 1.2.3+build ^1.2.3 true +gtr 1.2.3+build ^1.2.3 false +ltr 1.2.3+build ^1.2.3 false +satisfies 1.2.3+build ~1.2.3 true +gtr 1.2.3+build ~1.2.3 false +ltr 1.2.3+build ~1.2.3 false +satisfies 1.2.3+build ^0.2.3 false +gtr 1.2.3+build ^0.2.3 true +ltr 1.2.3+build ^0.2.3 false +satisfies 1.2.3+build ^0.0.3 false +gtr 1.2.3+build ^0.0.3 true +ltr 1.2.3+build ^0.0.3 false +satisfies 1.2.3+build 1.2.x true +gtr 1.2.3+build 1.2.x false +ltr 1.2.3+build 1.2.x false +satisfies 1.2.3+build 1.x true +gtr 1.2.3+build 1.x false +ltr 1.2.3+build 1.x false +satisfies 1.2.3+build * true +gtr 1.2.3+build * false +ltr 1.2.3+build * false +satisfies 1.2.3+build >=1.2.3 <2.0.0 true +gtr 1.2.3+build >=1.2.3 <2.0.0 false +ltr 1.2.3+build >=1.2.3 <2.0.0 false +satisfies 1.2.3+build 1.2.3 - 2.3.4 true +gtr 1.2.3+build 1.2.3 - 2.3.4 false +ltr 1.2.3+build 1.2.3 - 2.3.4 false +satisfies 1.2.3+build 1.2.3 || >=2.0.0 true +gtr 1.2.3+build 1.2.3 || >=2.0.0 false +ltr 1.2.3+build 1.2.3 || >=2.0.0 false +satisfies 1.2.3+build ^1.2.3-alpha true +gtr 1.2.3+build ^1.2.3-alpha false +ltr 1.2.3+build ^1.2.3-alpha false +satisfies 1.2.3+build ~1.2 true +gtr 1.2.3+build ~1.2 false +ltr 1.2.3+build ~1.2 false +satisfies 1.2.3+build ^1 true +gtr 1.2.3+build ^1 false +ltr 1.2.3+build ^1 false +satisfies 1.0.0+007 1.2.3 false +gtr 1.0.0+007 1.2.3 false +ltr 1.0.0+007 1.2.3 true +satisfies 1.0.0+007 =1.2.3 false +gtr 1.0.0+007 =1.2.3 false +ltr 1.0.0+007 =1.2.3 true +satisfies 1.0.0+007 >1.2.3 false +gtr 1.0.0+007 >1.2.3 false +ltr 1.0.0+007 >1.2.3 true +satisfies 1.0.0+007 >=1.2.3 false +gtr 1.0.0+007 >=1.2.3 false +ltr 1.0.0+007 >=1.2.3 true +satisfies 1.0.0+007 <1.2.3 true +gtr 1.0.0+007 <1.2.3 false +ltr 1.0.0+007 <1.2.3 false +satisfies 1.0.0+007 <=1.2.3 true +gtr 1.0.0+007 <=1.2.3 false +ltr 1.0.0+007 <=1.2.3 false +satisfies 1.0.0+007 ^1.2.3 false +gtr 1.0.0+007 ^1.2.3 false +ltr 1.0.0+007 ^1.2.3 true +satisfies 1.0.0+007 ~1.2.3 false +gtr 1.0.0+007 ~1.2.3 false +ltr 1.0.0+007 ~1.2.3 true +satisfies 1.0.0+007 ^0.2.3 false +gtr 1.0.0+007 ^0.2.3 true +ltr 1.0.0+007 ^0.2.3 false +satisfies 1.0.0+007 ^0.0.3 false +gtr 1.0.0+007 ^0.0.3 true +ltr 1.0.0+007 ^0.0.3 false +satisfies 1.0.0+007 1.2.x false +gtr 1.0.0+007 1.2.x false +ltr 1.0.0+007 1.2.x true +satisfies 1.0.0+007 1.x true +gtr 1.0.0+007 1.x false +ltr 1.0.0+007 1.x false +satisfies 1.0.0+007 * true +gtr 1.0.0+007 * false +ltr 1.0.0+007 * false +satisfies 1.0.0+007 >=1.2.3 <2.0.0 false +gtr 1.0.0+007 >=1.2.3 <2.0.0 false +ltr 1.0.0+007 >=1.2.3 <2.0.0 true +satisfies 1.0.0+007 1.2.3 - 2.3.4 false +gtr 1.0.0+007 1.2.3 - 2.3.4 false +ltr 1.0.0+007 1.2.3 - 2.3.4 true +satisfies 1.0.0+007 1.2.3 || >=2.0.0 false +gtr 1.0.0+007 1.2.3 || >=2.0.0 false +ltr 1.0.0+007 1.2.3 || >=2.0.0 true +satisfies 1.0.0+007 ^1.2.3-alpha false +gtr 1.0.0+007 ^1.2.3-alpha false +ltr 1.0.0+007 ^1.2.3-alpha true +satisfies 1.0.0+007 ~1.2 false +gtr 1.0.0+007 ~1.2 false +ltr 1.0.0+007 ~1.2 true +satisfies 1.0.0+007 ^1 true +gtr 1.0.0+007 ^1 false +ltr 1.0.0+007 ^1 false +satisfies 10.0.0 1.2.3 false +gtr 10.0.0 1.2.3 true +ltr 10.0.0 1.2.3 false +satisfies 10.0.0 =1.2.3 false +gtr 10.0.0 =1.2.3 true +ltr 10.0.0 =1.2.3 false +satisfies 10.0.0 >1.2.3 true +gtr 10.0.0 >1.2.3 false +ltr 10.0.0 >1.2.3 false +satisfies 10.0.0 >=1.2.3 true +gtr 10.0.0 >=1.2.3 false +ltr 10.0.0 >=1.2.3 false +satisfies 10.0.0 <1.2.3 false +gtr 10.0.0 <1.2.3 true +ltr 10.0.0 <1.2.3 false +satisfies 10.0.0 <=1.2.3 false +gtr 10.0.0 <=1.2.3 true +ltr 10.0.0 <=1.2.3 false +satisfies 10.0.0 ^1.2.3 false +gtr 10.0.0 ^1.2.3 true +ltr 10.0.0 ^1.2.3 false +satisfies 10.0.0 ~1.2.3 false +gtr 10.0.0 ~1.2.3 true +ltr 10.0.0 ~1.2.3 false +satisfies 10.0.0 ^0.2.3 false +gtr 10.0.0 ^0.2.3 true +ltr 10.0.0 ^0.2.3 false +satisfies 10.0.0 ^0.0.3 false +gtr 10.0.0 ^0.0.3 true +ltr 10.0.0 ^0.0.3 false +satisfies 10.0.0 1.2.x false +gtr 10.0.0 1.2.x true +ltr 10.0.0 1.2.x false +satisfies 10.0.0 1.x false +gtr 10.0.0 1.x true +ltr 10.0.0 1.x false +satisfies 10.0.0 * true +gtr 10.0.0 * false +ltr 10.0.0 * false +satisfies 10.0.0 >=1.2.3 <2.0.0 false +gtr 10.0.0 >=1.2.3 <2.0.0 true +ltr 10.0.0 >=1.2.3 <2.0.0 false +satisfies 10.0.0 1.2.3 - 2.3.4 false +gtr 10.0.0 1.2.3 - 2.3.4 true +ltr 10.0.0 1.2.3 - 2.3.4 false +satisfies 10.0.0 1.2.3 || >=2.0.0 true +gtr 10.0.0 1.2.3 || >=2.0.0 false +ltr 10.0.0 1.2.3 || >=2.0.0 false +satisfies 10.0.0 ^1.2.3-alpha false +gtr 10.0.0 ^1.2.3-alpha true +ltr 10.0.0 ^1.2.3-alpha false +satisfies 10.0.0 ~1.2 false +gtr 10.0.0 ~1.2 true +ltr 10.0.0 ~1.2 false +satisfies 10.0.0 ^1 false +gtr 10.0.0 ^1 true +ltr 10.0.0 ^1 false +intersects 1.2.3 1.2.3 true +intersects 1.2.3 =1.2.3 true +intersects 1.2.3 >1.2.3 false +intersects 1.2.3 >=1.2.3 true +intersects 1.2.3 <1.2.3 false +intersects 1.2.3 <=1.2.3 true +intersects 1.2.3 ^1.2.3 true +intersects 1.2.3 ~1.2.3 true +intersects 1.2.3 ^0.2.3 false +intersects 1.2.3 ^0.0.3 false +intersects 1.2.3 1.2.x true +intersects 1.2.3 1.x true +intersects 1.2.3 * true +intersects 1.2.3 >=1.2.3 <2.0.0 true +intersects 1.2.3 1.2.3 - 2.3.4 true +intersects 1.2.3 1.2.3 || >=2.0.0 true +intersects 1.2.3 ^1.2.3-alpha true +intersects 1.2.3 ~1.2 true +intersects 1.2.3 ^1 true +intersects =1.2.3 1.2.3 true +intersects =1.2.3 =1.2.3 true +intersects =1.2.3 >1.2.3 false +intersects =1.2.3 >=1.2.3 true +intersects =1.2.3 <1.2.3 false +intersects =1.2.3 <=1.2.3 true +intersects =1.2.3 ^1.2.3 true +intersects =1.2.3 ~1.2.3 true +intersects =1.2.3 ^0.2.3 false +intersects =1.2.3 ^0.0.3 false +intersects =1.2.3 1.2.x true +intersects =1.2.3 1.x true +intersects =1.2.3 * true +intersects =1.2.3 >=1.2.3 <2.0.0 true +intersects =1.2.3 1.2.3 - 2.3.4 true +intersects =1.2.3 1.2.3 || >=2.0.0 true +intersects =1.2.3 ^1.2.3-alpha true +intersects =1.2.3 ~1.2 true +intersects =1.2.3 ^1 true +intersects >1.2.3 1.2.3 false +intersects >1.2.3 =1.2.3 false +intersects >1.2.3 >1.2.3 true +intersects >1.2.3 >=1.2.3 true +intersects >1.2.3 <1.2.3 false +intersects >1.2.3 <=1.2.3 false +intersects >1.2.3 ^1.2.3 true +intersects >1.2.3 ~1.2.3 true +intersects >1.2.3 ^0.2.3 false +intersects >1.2.3 ^0.0.3 false +intersects >1.2.3 1.2.x true +intersects >1.2.3 1.x true +intersects >1.2.3 * true +intersects >1.2.3 >=1.2.3 <2.0.0 true +intersects >1.2.3 1.2.3 - 2.3.4 true +intersects >1.2.3 1.2.3 || >=2.0.0 true +intersects >1.2.3 ^1.2.3-alpha true +intersects >1.2.3 ~1.2 true +intersects >1.2.3 ^1 true +intersects >=1.2.3 1.2.3 true +intersects >=1.2.3 =1.2.3 true +intersects >=1.2.3 >1.2.3 true +intersects >=1.2.3 >=1.2.3 true +intersects >=1.2.3 <1.2.3 false +intersects >=1.2.3 <=1.2.3 true +intersects >=1.2.3 ^1.2.3 true +intersects >=1.2.3 ~1.2.3 true +intersects >=1.2.3 ^0.2.3 false +intersects >=1.2.3 ^0.0.3 false +intersects >=1.2.3 1.2.x true +intersects >=1.2.3 1.x true +intersects >=1.2.3 * true +intersects >=1.2.3 >=1.2.3 <2.0.0 true +intersects >=1.2.3 1.2.3 - 2.3.4 true +intersects >=1.2.3 1.2.3 || >=2.0.0 true +intersects >=1.2.3 ^1.2.3-alpha true +intersects >=1.2.3 ~1.2 true +intersects >=1.2.3 ^1 true +intersects <1.2.3 1.2.3 false +intersects <1.2.3 =1.2.3 false +intersects <1.2.3 >1.2.3 false +intersects <1.2.3 >=1.2.3 false +intersects <1.2.3 <1.2.3 true +intersects <1.2.3 <=1.2.3 true +intersects <1.2.3 ^1.2.3 false +intersects <1.2.3 ~1.2.3 false +intersects <1.2.3 ^0.2.3 true +intersects <1.2.3 ^0.0.3 true +intersects <1.2.3 1.2.x true +intersects <1.2.3 1.x true +intersects <1.2.3 * true +intersects <1.2.3 >=1.2.3 <2.0.0 false +intersects <1.2.3 1.2.3 - 2.3.4 false +intersects <1.2.3 1.2.3 || >=2.0.0 false +intersects <1.2.3 ^1.2.3-alpha true +intersects <1.2.3 ~1.2 true +intersects <1.2.3 ^1 true +intersects <=1.2.3 1.2.3 true +intersects <=1.2.3 =1.2.3 true +intersects <=1.2.3 >1.2.3 false +intersects <=1.2.3 >=1.2.3 true +intersects <=1.2.3 <1.2.3 true +intersects <=1.2.3 <=1.2.3 true +intersects <=1.2.3 ^1.2.3 true +intersects <=1.2.3 ~1.2.3 true +intersects <=1.2.3 ^0.2.3 true +intersects <=1.2.3 ^0.0.3 true +intersects <=1.2.3 1.2.x true +intersects <=1.2.3 1.x true +intersects <=1.2.3 * true +intersects <=1.2.3 >=1.2.3 <2.0.0 true +intersects <=1.2.3 1.2.3 - 2.3.4 true +intersects <=1.2.3 1.2.3 || >=2.0.0 true +intersects <=1.2.3 ^1.2.3-alpha true +intersects <=1.2.3 ~1.2 true +intersects <=1.2.3 ^1 true +intersects ^1.2.3 1.2.3 true +intersects ^1.2.3 =1.2.3 true +intersects ^1.2.3 >1.2.3 true +intersects ^1.2.3 >=1.2.3 true +intersects ^1.2.3 <1.2.3 false +intersects ^1.2.3 <=1.2.3 true +intersects ^1.2.3 ^1.2.3 true +intersects ^1.2.3 ~1.2.3 true +intersects ^1.2.3 ^0.2.3 false +intersects ^1.2.3 ^0.0.3 false +intersects ^1.2.3 1.2.x true +intersects ^1.2.3 1.x true +intersects ^1.2.3 * true +intersects ^1.2.3 >=1.2.3 <2.0.0 true +intersects ^1.2.3 1.2.3 - 2.3.4 true +intersects ^1.2.3 1.2.3 || >=2.0.0 true +intersects ^1.2.3 ^1.2.3-alpha true +intersects ^1.2.3 ~1.2 true +intersects ^1.2.3 ^1 true +intersects ~1.2.3 1.2.3 true +intersects ~1.2.3 =1.2.3 true +intersects ~1.2.3 >1.2.3 true +intersects ~1.2.3 >=1.2.3 true +intersects ~1.2.3 <1.2.3 false +intersects ~1.2.3 <=1.2.3 true +intersects ~1.2.3 ^1.2.3 true +intersects ~1.2.3 ~1.2.3 true +intersects ~1.2.3 ^0.2.3 false +intersects ~1.2.3 ^0.0.3 false +intersects ~1.2.3 1.2.x true +intersects ~1.2.3 1.x true +intersects ~1.2.3 * true +intersects ~1.2.3 >=1.2.3 <2.0.0 true +intersects ~1.2.3 1.2.3 - 2.3.4 true +intersects ~1.2.3 1.2.3 || >=2.0.0 true +intersects ~1.2.3 ^1.2.3-alpha true +intersects ~1.2.3 ~1.2 true +intersects ~1.2.3 ^1 true +intersects ^0.2.3 1.2.3 false +intersects ^0.2.3 =1.2.3 false +intersects ^0.2.3 >1.2.3 false +intersects ^0.2.3 >=1.2.3 false +intersects ^0.2.3 <1.2.3 true +intersects ^0.2.3 <=1.2.3 true +intersects ^0.2.3 ^1.2.3 false +intersects ^0.2.3 ~1.2.3 false +intersects ^0.2.3 ^0.2.3 true +intersects ^0.2.3 ^0.0.3 false +intersects ^0.2.3 1.2.x false +intersects ^0.2.3 1.x false +intersects ^0.2.3 * true +intersects ^0.2.3 >=1.2.3 <2.0.0 false +intersects ^0.2.3 1.2.3 - 2.3.4 false +intersects ^0.2.3 1.2.3 || >=2.0.0 false +intersects ^0.2.3 ^1.2.3-alpha false +intersects ^0.2.3 ~1.2 false +intersects ^0.2.3 ^1 false +intersects ^0.0.3 1.2.3 false +intersects ^0.0.3 =1.2.3 false +intersects ^0.0.3 >1.2.3 false +intersects ^0.0.3 >=1.2.3 false +intersects ^0.0.3 <1.2.3 true +intersects ^0.0.3 <=1.2.3 true +intersects ^0.0.3 ^1.2.3 false +intersects ^0.0.3 ~1.2.3 false +intersects ^0.0.3 ^0.2.3 false +intersects ^0.0.3 ^0.0.3 true +intersects ^0.0.3 1.2.x false +intersects ^0.0.3 1.x false +intersects ^0.0.3 * true +intersects ^0.0.3 >=1.2.3 <2.0.0 false +intersects ^0.0.3 1.2.3 - 2.3.4 false +intersects ^0.0.3 1.2.3 || >=2.0.0 false +intersects ^0.0.3 ^1.2.3-alpha false +intersects ^0.0.3 ~1.2 false +intersects ^0.0.3 ^1 false +intersects 1.2.x 1.2.3 true +intersects 1.2.x =1.2.3 true +intersects 1.2.x >1.2.3 true +intersects 1.2.x >=1.2.3 true +intersects 1.2.x <1.2.3 true +intersects 1.2.x <=1.2.3 true +intersects 1.2.x ^1.2.3 true +intersects 1.2.x ~1.2.3 true +intersects 1.2.x ^0.2.3 false +intersects 1.2.x ^0.0.3 false +intersects 1.2.x 1.2.x true +intersects 1.2.x 1.x true +intersects 1.2.x * true +intersects 1.2.x >=1.2.3 <2.0.0 true +intersects 1.2.x 1.2.3 - 2.3.4 true +intersects 1.2.x 1.2.3 || >=2.0.0 true +intersects 1.2.x ^1.2.3-alpha true +intersects 1.2.x ~1.2 true +intersects 1.2.x ^1 true +intersects 1.x 1.2.3 true +intersects 1.x =1.2.3 true +intersects 1.x >1.2.3 true +intersects 1.x >=1.2.3 true +intersects 1.x <1.2.3 true +intersects 1.x <=1.2.3 true +intersects 1.x ^1.2.3 true +intersects 1.x ~1.2.3 true +intersects 1.x ^0.2.3 false +intersects 1.x ^0.0.3 false +intersects 1.x 1.2.x true +intersects 1.x 1.x true +intersects 1.x * true +intersects 1.x >=1.2.3 <2.0.0 true +intersects 1.x 1.2.3 - 2.3.4 true +intersects 1.x 1.2.3 || >=2.0.0 true +intersects 1.x ^1.2.3-alpha true +intersects 1.x ~1.2 true +intersects 1.x ^1 true +intersects * 1.2.3 true +intersects * =1.2.3 true +intersects * >1.2.3 true +intersects * >=1.2.3 true +intersects * <1.2.3 true +intersects * <=1.2.3 true +intersects * ^1.2.3 true +intersects * ~1.2.3 true +intersects * ^0.2.3 true +intersects * ^0.0.3 true +intersects * 1.2.x true +intersects * 1.x true +intersects * * true +intersects * >=1.2.3 <2.0.0 true +intersects * 1.2.3 - 2.3.4 true +intersects * 1.2.3 || >=2.0.0 true +intersects * ^1.2.3-alpha true +intersects * ~1.2 true +intersects * ^1 true +intersects >=1.2.3 <2.0.0 1.2.3 true +intersects >=1.2.3 <2.0.0 =1.2.3 true +intersects >=1.2.3 <2.0.0 >1.2.3 true +intersects >=1.2.3 <2.0.0 >=1.2.3 true +intersects >=1.2.3 <2.0.0 <1.2.3 false +intersects >=1.2.3 <2.0.0 <=1.2.3 true +intersects >=1.2.3 <2.0.0 ^1.2.3 true +intersects >=1.2.3 <2.0.0 ~1.2.3 true +intersects >=1.2.3 <2.0.0 ^0.2.3 false +intersects >=1.2.3 <2.0.0 ^0.0.3 false +intersects >=1.2.3 <2.0.0 1.2.x true +intersects >=1.2.3 <2.0.0 1.x true +intersects >=1.2.3 <2.0.0 * true +intersects >=1.2.3 <2.0.0 >=1.2.3 <2.0.0 true +intersects >=1.2.3 <2.0.0 1.2.3 - 2.3.4 true +intersects >=1.2.3 <2.0.0 1.2.3 || >=2.0.0 true +intersects >=1.2.3 <2.0.0 ^1.2.3-alpha true +intersects >=1.2.3 <2.0.0 ~1.2 true +intersects >=1.2.3 <2.0.0 ^1 true +intersects 1.2.3 - 2.3.4 1.2.3 true +intersects 1.2.3 - 2.3.4 =1.2.3 true +intersects 1.2.3 - 2.3.4 >1.2.3 true +intersects 1.2.3 - 2.3.4 >=1.2.3 true +intersects 1.2.3 - 2.3.4 <1.2.3 false +intersects 1.2.3 - 2.3.4 <=1.2.3 true +intersects 1.2.3 - 2.3.4 ^1.2.3 true +intersects 1.2.3 - 2.3.4 ~1.2.3 true +intersects 1.2.3 - 2.3.4 ^0.2.3 false +intersects 1.2.3 - 2.3.4 ^0.0.3 false +intersects 1.2.3 - 2.3.4 1.2.x true +intersects 1.2.3 - 2.3.4 1.x true +intersects 1.2.3 - 2.3.4 * true +intersects 1.2.3 - 2.3.4 >=1.2.3 <2.0.0 true +intersects 1.2.3 - 2.3.4 1.2.3 - 2.3.4 true +intersects 1.2.3 - 2.3.4 1.2.3 || >=2.0.0 true +intersects 1.2.3 - 2.3.4 ^1.2.3-alpha true +intersects 1.2.3 - 2.3.4 ~1.2 true +intersects 1.2.3 - 2.3.4 ^1 true +intersects 1.2.3 || >=2.0.0 1.2.3 true +intersects 1.2.3 || >=2.0.0 =1.2.3 true +intersects 1.2.3 || >=2.0.0 >1.2.3 true +intersects 1.2.3 || >=2.0.0 >=1.2.3 true +intersects 1.2.3 || >=2.0.0 <1.2.3 false +intersects 1.2.3 || >=2.0.0 <=1.2.3 true +intersects 1.2.3 || >=2.0.0 ^1.2.3 true +intersects 1.2.3 || >=2.0.0 ~1.2.3 true +intersects 1.2.3 || >=2.0.0 ^0.2.3 false +intersects 1.2.3 || >=2.0.0 ^0.0.3 false +intersects 1.2.3 || >=2.0.0 1.2.x true +intersects 1.2.3 || >=2.0.0 1.x true +intersects 1.2.3 || >=2.0.0 * true +intersects 1.2.3 || >=2.0.0 >=1.2.3 <2.0.0 true +intersects 1.2.3 || >=2.0.0 1.2.3 - 2.3.4 true +intersects 1.2.3 || >=2.0.0 1.2.3 || >=2.0.0 true +intersects 1.2.3 || >=2.0.0 ^1.2.3-alpha true +intersects 1.2.3 || >=2.0.0 ~1.2 true +intersects 1.2.3 || >=2.0.0 ^1 true +intersects ^1.2.3-alpha 1.2.3 true +intersects ^1.2.3-alpha =1.2.3 true +intersects ^1.2.3-alpha >1.2.3 true +intersects ^1.2.3-alpha >=1.2.3 true +intersects ^1.2.3-alpha <1.2.3 true +intersects ^1.2.3-alpha <=1.2.3 true +intersects ^1.2.3-alpha ^1.2.3 true +intersects ^1.2.3-alpha ~1.2.3 true +intersects ^1.2.3-alpha ^0.2.3 false +intersects ^1.2.3-alpha ^0.0.3 false +intersects ^1.2.3-alpha 1.2.x true +intersects ^1.2.3-alpha 1.x true +intersects ^1.2.3-alpha * true +intersects ^1.2.3-alpha >=1.2.3 <2.0.0 true +intersects ^1.2.3-alpha 1.2.3 - 2.3.4 true +intersects ^1.2.3-alpha 1.2.3 || >=2.0.0 true +intersects ^1.2.3-alpha ^1.2.3-alpha true +intersects ^1.2.3-alpha ~1.2 true +intersects ^1.2.3-alpha ^1 true +intersects ~1.2 1.2.3 true +intersects ~1.2 =1.2.3 true +intersects ~1.2 >1.2.3 true +intersects ~1.2 >=1.2.3 true +intersects ~1.2 <1.2.3 true +intersects ~1.2 <=1.2.3 true +intersects ~1.2 ^1.2.3 true +intersects ~1.2 ~1.2.3 true +intersects ~1.2 ^0.2.3 false +intersects ~1.2 ^0.0.3 false +intersects ~1.2 1.2.x true +intersects ~1.2 1.x true +intersects ~1.2 * true +intersects ~1.2 >=1.2.3 <2.0.0 true +intersects ~1.2 1.2.3 - 2.3.4 true +intersects ~1.2 1.2.3 || >=2.0.0 true +intersects ~1.2 ^1.2.3-alpha true +intersects ~1.2 ~1.2 true +intersects ~1.2 ^1 true +intersects ^1 1.2.3 true +intersects ^1 =1.2.3 true +intersects ^1 >1.2.3 true +intersects ^1 >=1.2.3 true +intersects ^1 <1.2.3 true +intersects ^1 <=1.2.3 true +intersects ^1 ^1.2.3 true +intersects ^1 ~1.2.3 true +intersects ^1 ^0.2.3 false +intersects ^1 ^0.0.3 false +intersects ^1 1.2.x true +intersects ^1 1.x true +intersects ^1 * true +intersects ^1 >=1.2.3 <2.0.0 true +intersects ^1 1.2.3 - 2.3.4 true +intersects ^1 1.2.3 || >=2.0.0 true +intersects ^1 ^1.2.3-alpha true +intersects ^1 ~1.2 true +intersects ^1 ^1 true +compare 0.0.0 0.0.0 0 +compare 0.0.0 1.0.0 -1 +compare 0.0.0 1.2.3 -1 +compare 0.0.0 1.2.4 -1 +compare 0.0.0 1.3.0 -1 +compare 0.0.0 2.0.0 -1 +compare 0.0.0 1.0.0-alpha -1 +compare 0.0.0 1.0.0-alpha.1 -1 +compare 0.0.0 1.0.0-alpha.beta -1 +compare 0.0.0 1.0.0-beta -1 +compare 0.0.0 1.0.0-beta.2 -1 +compare 0.0.0 1.0.0-beta.11 -1 +compare 0.0.0 1.0.0-rc.1 -1 +compare 0.0.0 1.2.3-0 -1 +compare 0.0.0 2.0.0-0 -1 +compare 0.0.0 1.2.3+build -1 +compare 0.0.0 1.0.0+007 -1 +compare 0.0.0 10.0.0 -1 +compare 1.0.0 0.0.0 1 +compare 1.0.0 1.0.0 0 +compare 1.0.0 1.2.3 -1 +compare 1.0.0 1.2.4 -1 +compare 1.0.0 1.3.0 -1 +compare 1.0.0 2.0.0 -1 +compare 1.0.0 1.0.0-alpha 1 +compare 1.0.0 1.0.0-alpha.1 1 +compare 1.0.0 1.0.0-alpha.beta 1 +compare 1.0.0 1.0.0-beta 1 +compare 1.0.0 1.0.0-beta.2 1 +compare 1.0.0 1.0.0-beta.11 1 +compare 1.0.0 1.0.0-rc.1 1 +compare 1.0.0 1.2.3-0 -1 +compare 1.0.0 2.0.0-0 -1 +compare 1.0.0 1.2.3+build -1 +compare 1.0.0 1.0.0+007 0 +compare 1.0.0 10.0.0 -1 +compare 1.2.3 0.0.0 1 +compare 1.2.3 1.0.0 1 +compare 1.2.3 1.2.3 0 +compare 1.2.3 1.2.4 -1 +compare 1.2.3 1.3.0 -1 +compare 1.2.3 2.0.0 -1 +compare 1.2.3 1.0.0-alpha 1 +compare 1.2.3 1.0.0-alpha.1 1 +compare 1.2.3 1.0.0-alpha.beta 1 +compare 1.2.3 1.0.0-beta 1 +compare 1.2.3 1.0.0-beta.2 1 +compare 1.2.3 1.0.0-beta.11 1 +compare 1.2.3 1.0.0-rc.1 1 +compare 1.2.3 1.2.3-0 1 +compare 1.2.3 2.0.0-0 -1 +compare 1.2.3 1.2.3+build 0 +compare 1.2.3 1.0.0+007 1 +compare 1.2.3 10.0.0 -1 +compare 1.2.4 0.0.0 1 +compare 1.2.4 1.0.0 1 +compare 1.2.4 1.2.3 1 +compare 1.2.4 1.2.4 0 +compare 1.2.4 1.3.0 -1 +compare 1.2.4 2.0.0 -1 +compare 1.2.4 1.0.0-alpha 1 +compare 1.2.4 1.0.0-alpha.1 1 +compare 1.2.4 1.0.0-alpha.beta 1 +compare 1.2.4 1.0.0-beta 1 +compare 1.2.4 1.0.0-beta.2 1 +compare 1.2.4 1.0.0-beta.11 1 +compare 1.2.4 1.0.0-rc.1 1 +compare 1.2.4 1.2.3-0 1 +compare 1.2.4 2.0.0-0 -1 +compare 1.2.4 1.2.3+build 1 +compare 1.2.4 1.0.0+007 1 +compare 1.2.4 10.0.0 -1 +compare 1.3.0 0.0.0 1 +compare 1.3.0 1.0.0 1 +compare 1.3.0 1.2.3 1 +compare 1.3.0 1.2.4 1 +compare 1.3.0 1.3.0 0 +compare 1.3.0 2.0.0 -1 +compare 1.3.0 1.0.0-alpha 1 +compare 1.3.0 1.0.0-alpha.1 1 +compare 1.3.0 1.0.0-alpha.beta 1 +compare 1.3.0 1.0.0-beta 1 +compare 1.3.0 1.0.0-beta.2 1 +compare 1.3.0 1.0.0-beta.11 1 +compare 1.3.0 1.0.0-rc.1 1 +compare 1.3.0 1.2.3-0 1 +compare 1.3.0 2.0.0-0 -1 +compare 1.3.0 1.2.3+build 1 +compare 1.3.0 1.0.0+007 1 +compare 1.3.0 10.0.0 -1 +compare 2.0.0 0.0.0 1 +compare 2.0.0 1.0.0 1 +compare 2.0.0 1.2.3 1 +compare 2.0.0 1.2.4 1 +compare 2.0.0 1.3.0 1 +compare 2.0.0 2.0.0 0 +compare 2.0.0 1.0.0-alpha 1 +compare 2.0.0 1.0.0-alpha.1 1 +compare 2.0.0 1.0.0-alpha.beta 1 +compare 2.0.0 1.0.0-beta 1 +compare 2.0.0 1.0.0-beta.2 1 +compare 2.0.0 1.0.0-beta.11 1 +compare 2.0.0 1.0.0-rc.1 1 +compare 2.0.0 1.2.3-0 1 +compare 2.0.0 2.0.0-0 1 +compare 2.0.0 1.2.3+build 1 +compare 2.0.0 1.0.0+007 1 +compare 2.0.0 10.0.0 -1 +compare 1.0.0-alpha 0.0.0 1 +compare 1.0.0-alpha 1.0.0 -1 +compare 1.0.0-alpha 1.2.3 -1 +compare 1.0.0-alpha 1.2.4 -1 +compare 1.0.0-alpha 1.3.0 -1 +compare 1.0.0-alpha 2.0.0 -1 +compare 1.0.0-alpha 1.0.0-alpha 0 +compare 1.0.0-alpha 1.0.0-alpha.1 -1 +compare 1.0.0-alpha 1.0.0-alpha.beta -1 +compare 1.0.0-alpha 1.0.0-beta -1 +compare 1.0.0-alpha 1.0.0-beta.2 -1 +compare 1.0.0-alpha 1.0.0-beta.11 -1 +compare 1.0.0-alpha 1.0.0-rc.1 -1 +compare 1.0.0-alpha 1.2.3-0 -1 +compare 1.0.0-alpha 2.0.0-0 -1 +compare 1.0.0-alpha 1.2.3+build -1 +compare 1.0.0-alpha 1.0.0+007 -1 +compare 1.0.0-alpha 10.0.0 -1 +compare 1.0.0-alpha.1 0.0.0 1 +compare 1.0.0-alpha.1 1.0.0 -1 +compare 1.0.0-alpha.1 1.2.3 -1 +compare 1.0.0-alpha.1 1.2.4 -1 +compare 1.0.0-alpha.1 1.3.0 -1 +compare 1.0.0-alpha.1 2.0.0 -1 +compare 1.0.0-alpha.1 1.0.0-alpha 1 +compare 1.0.0-alpha.1 1.0.0-alpha.1 0 +compare 1.0.0-alpha.1 1.0.0-alpha.beta -1 +compare 1.0.0-alpha.1 1.0.0-beta -1 +compare 1.0.0-alpha.1 1.0.0-beta.2 -1 +compare 1.0.0-alpha.1 1.0.0-beta.11 -1 +compare 1.0.0-alpha.1 1.0.0-rc.1 -1 +compare 1.0.0-alpha.1 1.2.3-0 -1 +compare 1.0.0-alpha.1 2.0.0-0 -1 +compare 1.0.0-alpha.1 1.2.3+build -1 +compare 1.0.0-alpha.1 1.0.0+007 -1 +compare 1.0.0-alpha.1 10.0.0 -1 +compare 1.0.0-alpha.beta 0.0.0 1 +compare 1.0.0-alpha.beta 1.0.0 -1 +compare 1.0.0-alpha.beta 1.2.3 -1 +compare 1.0.0-alpha.beta 1.2.4 -1 +compare 1.0.0-alpha.beta 1.3.0 -1 +compare 1.0.0-alpha.beta 2.0.0 -1 +compare 1.0.0-alpha.beta 1.0.0-alpha 1 +compare 1.0.0-alpha.beta 1.0.0-alpha.1 1 +compare 1.0.0-alpha.beta 1.0.0-alpha.beta 0 +compare 1.0.0-alpha.beta 1.0.0-beta -1 +compare 1.0.0-alpha.beta 1.0.0-beta.2 -1 +compare 1.0.0-alpha.beta 1.0.0-beta.11 -1 +compare 1.0.0-alpha.beta 1.0.0-rc.1 -1 +compare 1.0.0-alpha.beta 1.2.3-0 -1 +compare 1.0.0-alpha.beta 2.0.0-0 -1 +compare 1.0.0-alpha.beta 1.2.3+build -1 +compare 1.0.0-alpha.beta 1.0.0+007 -1 +compare 1.0.0-alpha.beta 10.0.0 -1 +compare 1.0.0-beta 0.0.0 1 +compare 1.0.0-beta 1.0.0 -1 +compare 1.0.0-beta 1.2.3 -1 +compare 1.0.0-beta 1.2.4 -1 +compare 1.0.0-beta 1.3.0 -1 +compare 1.0.0-beta 2.0.0 -1 +compare 1.0.0-beta 1.0.0-alpha 1 +compare 1.0.0-beta 1.0.0-alpha.1 1 +compare 1.0.0-beta 1.0.0-alpha.beta 1 +compare 1.0.0-beta 1.0.0-beta 0 +compare 1.0.0-beta 1.0.0-beta.2 -1 +compare 1.0.0-beta 1.0.0-beta.11 -1 +compare 1.0.0-beta 1.0.0-rc.1 -1 +compare 1.0.0-beta 1.2.3-0 -1 +compare 1.0.0-beta 2.0.0-0 -1 +compare 1.0.0-beta 1.2.3+build -1 +compare 1.0.0-beta 1.0.0+007 -1 +compare 1.0.0-beta 10.0.0 -1 +compare 1.0.0-beta.2 0.0.0 1 +compare 1.0.0-beta.2 1.0.0 -1 +compare 1.0.0-beta.2 1.2.3 -1 +compare 1.0.0-beta.2 1.2.4 -1 +compare 1.0.0-beta.2 1.3.0 -1 +compare 1.0.0-beta.2 2.0.0 -1 +compare 1.0.0-beta.2 1.0.0-alpha 1 +compare 1.0.0-beta.2 1.0.0-alpha.1 1 +compare 1.0.0-beta.2 1.0.0-alpha.beta 1 +compare 1.0.0-beta.2 1.0.0-beta 1 +compare 1.0.0-beta.2 1.0.0-beta.2 0 +compare 1.0.0-beta.2 1.0.0-beta.11 -1 +compare 1.0.0-beta.2 1.0.0-rc.1 -1 +compare 1.0.0-beta.2 1.2.3-0 -1 +compare 1.0.0-beta.2 2.0.0-0 -1 +compare 1.0.0-beta.2 1.2.3+build -1 +compare 1.0.0-beta.2 1.0.0+007 -1 +compare 1.0.0-beta.2 10.0.0 -1 +compare 1.0.0-beta.11 0.0.0 1 +compare 1.0.0-beta.11 1.0.0 -1 +compare 1.0.0-beta.11 1.2.3 -1 +compare 1.0.0-beta.11 1.2.4 -1 +compare 1.0.0-beta.11 1.3.0 -1 +compare 1.0.0-beta.11 2.0.0 -1 +compare 1.0.0-beta.11 1.0.0-alpha 1 +compare 1.0.0-beta.11 1.0.0-alpha.1 1 +compare 1.0.0-beta.11 1.0.0-alpha.beta 1 +compare 1.0.0-beta.11 1.0.0-beta 1 +compare 1.0.0-beta.11 1.0.0-beta.2 1 +compare 1.0.0-beta.11 1.0.0-beta.11 0 +compare 1.0.0-beta.11 1.0.0-rc.1 -1 +compare 1.0.0-beta.11 1.2.3-0 -1 +compare 1.0.0-beta.11 2.0.0-0 -1 +compare 1.0.0-beta.11 1.2.3+build -1 +compare 1.0.0-beta.11 1.0.0+007 -1 +compare 1.0.0-beta.11 10.0.0 -1 +compare 1.0.0-rc.1 0.0.0 1 +compare 1.0.0-rc.1 1.0.0 -1 +compare 1.0.0-rc.1 1.2.3 -1 +compare 1.0.0-rc.1 1.2.4 -1 +compare 1.0.0-rc.1 1.3.0 -1 +compare 1.0.0-rc.1 2.0.0 -1 +compare 1.0.0-rc.1 1.0.0-alpha 1 +compare 1.0.0-rc.1 1.0.0-alpha.1 1 +compare 1.0.0-rc.1 1.0.0-alpha.beta 1 +compare 1.0.0-rc.1 1.0.0-beta 1 +compare 1.0.0-rc.1 1.0.0-beta.2 1 +compare 1.0.0-rc.1 1.0.0-beta.11 1 +compare 1.0.0-rc.1 1.0.0-rc.1 0 +compare 1.0.0-rc.1 1.2.3-0 -1 +compare 1.0.0-rc.1 2.0.0-0 -1 +compare 1.0.0-rc.1 1.2.3+build -1 +compare 1.0.0-rc.1 1.0.0+007 -1 +compare 1.0.0-rc.1 10.0.0 -1 +compare 1.2.3-0 0.0.0 1 +compare 1.2.3-0 1.0.0 1 +compare 1.2.3-0 1.2.3 -1 +compare 1.2.3-0 1.2.4 -1 +compare 1.2.3-0 1.3.0 -1 +compare 1.2.3-0 2.0.0 -1 +compare 1.2.3-0 1.0.0-alpha 1 +compare 1.2.3-0 1.0.0-alpha.1 1 +compare 1.2.3-0 1.0.0-alpha.beta 1 +compare 1.2.3-0 1.0.0-beta 1 +compare 1.2.3-0 1.0.0-beta.2 1 +compare 1.2.3-0 1.0.0-beta.11 1 +compare 1.2.3-0 1.0.0-rc.1 1 +compare 1.2.3-0 1.2.3-0 0 +compare 1.2.3-0 2.0.0-0 -1 +compare 1.2.3-0 1.2.3+build -1 +compare 1.2.3-0 1.0.0+007 1 +compare 1.2.3-0 10.0.0 -1 +compare 2.0.0-0 0.0.0 1 +compare 2.0.0-0 1.0.0 1 +compare 2.0.0-0 1.2.3 1 +compare 2.0.0-0 1.2.4 1 +compare 2.0.0-0 1.3.0 1 +compare 2.0.0-0 2.0.0 -1 +compare 2.0.0-0 1.0.0-alpha 1 +compare 2.0.0-0 1.0.0-alpha.1 1 +compare 2.0.0-0 1.0.0-alpha.beta 1 +compare 2.0.0-0 1.0.0-beta 1 +compare 2.0.0-0 1.0.0-beta.2 1 +compare 2.0.0-0 1.0.0-beta.11 1 +compare 2.0.0-0 1.0.0-rc.1 1 +compare 2.0.0-0 1.2.3-0 1 +compare 2.0.0-0 2.0.0-0 0 +compare 2.0.0-0 1.2.3+build 1 +compare 2.0.0-0 1.0.0+007 1 +compare 2.0.0-0 10.0.0 -1 +compare 1.2.3+build 0.0.0 1 +compare 1.2.3+build 1.0.0 1 +compare 1.2.3+build 1.2.3 0 +compare 1.2.3+build 1.2.4 -1 +compare 1.2.3+build 1.3.0 -1 +compare 1.2.3+build 2.0.0 -1 +compare 1.2.3+build 1.0.0-alpha 1 +compare 1.2.3+build 1.0.0-alpha.1 1 +compare 1.2.3+build 1.0.0-alpha.beta 1 +compare 1.2.3+build 1.0.0-beta 1 +compare 1.2.3+build 1.0.0-beta.2 1 +compare 1.2.3+build 1.0.0-beta.11 1 +compare 1.2.3+build 1.0.0-rc.1 1 +compare 1.2.3+build 1.2.3-0 1 +compare 1.2.3+build 2.0.0-0 -1 +compare 1.2.3+build 1.2.3+build 0 +compare 1.2.3+build 1.0.0+007 1 +compare 1.2.3+build 10.0.0 -1 +compare 1.0.0+007 0.0.0 1 +compare 1.0.0+007 1.0.0 0 +compare 1.0.0+007 1.2.3 -1 +compare 1.0.0+007 1.2.4 -1 +compare 1.0.0+007 1.3.0 -1 +compare 1.0.0+007 2.0.0 -1 +compare 1.0.0+007 1.0.0-alpha 1 +compare 1.0.0+007 1.0.0-alpha.1 1 +compare 1.0.0+007 1.0.0-alpha.beta 1 +compare 1.0.0+007 1.0.0-beta 1 +compare 1.0.0+007 1.0.0-beta.2 1 +compare 1.0.0+007 1.0.0-beta.11 1 +compare 1.0.0+007 1.0.0-rc.1 1 +compare 1.0.0+007 1.2.3-0 -1 +compare 1.0.0+007 2.0.0-0 -1 +compare 1.0.0+007 1.2.3+build -1 +compare 1.0.0+007 1.0.0+007 0 +compare 1.0.0+007 10.0.0 -1 +compare 10.0.0 0.0.0 1 +compare 10.0.0 1.0.0 1 +compare 10.0.0 1.2.3 1 +compare 10.0.0 1.2.4 1 +compare 10.0.0 1.3.0 1 +compare 10.0.0 2.0.0 1 +compare 10.0.0 1.0.0-alpha 1 +compare 10.0.0 1.0.0-alpha.1 1 +compare 10.0.0 1.0.0-alpha.beta 1 +compare 10.0.0 1.0.0-beta 1 +compare 10.0.0 1.0.0-beta.2 1 +compare 10.0.0 1.0.0-beta.11 1 +compare 10.0.0 1.0.0-rc.1 1 +compare 10.0.0 1.2.3-0 1 +compare 10.0.0 2.0.0-0 1 +compare 10.0.0 1.2.3+build 1 +compare 10.0.0 1.0.0+007 1 +compare 10.0.0 10.0.0 0 +diff 0.0.0 0.0.0 NULL +diff 0.0.0 1.0.0 major +diff 0.0.0 1.2.3 major +diff 0.0.0 1.2.4 major +diff 0.0.0 1.3.0 major +diff 0.0.0 2.0.0 major +diff 0.0.0 1.0.0-alpha premajor +diff 0.0.0 1.0.0-alpha.1 premajor +diff 0.0.0 1.0.0-alpha.beta premajor +diff 0.0.0 1.0.0-beta premajor +diff 0.0.0 1.0.0-beta.2 premajor +diff 0.0.0 1.0.0-beta.11 premajor +diff 0.0.0 1.0.0-rc.1 premajor +diff 0.0.0 1.2.3-0 premajor +diff 0.0.0 2.0.0-0 premajor +diff 0.0.0 1.2.3+build major +diff 0.0.0 1.0.0+007 major +diff 0.0.0 10.0.0 major +diff 1.0.0 0.0.0 major +diff 1.0.0 1.0.0 NULL +diff 1.0.0 1.2.3 minor +diff 1.0.0 1.2.4 minor +diff 1.0.0 1.3.0 minor +diff 1.0.0 2.0.0 major +diff 1.0.0 1.0.0-alpha major +diff 1.0.0 1.0.0-alpha.1 major +diff 1.0.0 1.0.0-alpha.beta major +diff 1.0.0 1.0.0-beta major +diff 1.0.0 1.0.0-beta.2 major +diff 1.0.0 1.0.0-beta.11 major +diff 1.0.0 1.0.0-rc.1 major +diff 1.0.0 1.2.3-0 preminor +diff 1.0.0 2.0.0-0 premajor +diff 1.0.0 1.2.3+build minor +diff 1.0.0 1.0.0+007 NULL +diff 1.0.0 10.0.0 major +diff 1.2.3 0.0.0 major +diff 1.2.3 1.0.0 minor +diff 1.2.3 1.2.3 NULL +diff 1.2.3 1.2.4 patch +diff 1.2.3 1.3.0 minor +diff 1.2.3 2.0.0 major +diff 1.2.3 1.0.0-alpha major +diff 1.2.3 1.0.0-alpha.1 major +diff 1.2.3 1.0.0-alpha.beta major +diff 1.2.3 1.0.0-beta major +diff 1.2.3 1.0.0-beta.2 major +diff 1.2.3 1.0.0-beta.11 major +diff 1.2.3 1.0.0-rc.1 major +diff 1.2.3 1.2.3-0 patch +diff 1.2.3 2.0.0-0 premajor +diff 1.2.3 1.2.3+build NULL +diff 1.2.3 1.0.0+007 minor +diff 1.2.3 10.0.0 major +diff 1.2.4 0.0.0 major +diff 1.2.4 1.0.0 minor +diff 1.2.4 1.2.3 patch +diff 1.2.4 1.2.4 NULL +diff 1.2.4 1.3.0 minor +diff 1.2.4 2.0.0 major +diff 1.2.4 1.0.0-alpha major +diff 1.2.4 1.0.0-alpha.1 major +diff 1.2.4 1.0.0-alpha.beta major +diff 1.2.4 1.0.0-beta major +diff 1.2.4 1.0.0-beta.2 major +diff 1.2.4 1.0.0-beta.11 major +diff 1.2.4 1.0.0-rc.1 major +diff 1.2.4 1.2.3-0 patch +diff 1.2.4 2.0.0-0 premajor +diff 1.2.4 1.2.3+build patch +diff 1.2.4 1.0.0+007 minor +diff 1.2.4 10.0.0 major +diff 1.3.0 0.0.0 major +diff 1.3.0 1.0.0 minor +diff 1.3.0 1.2.3 minor +diff 1.3.0 1.2.4 minor +diff 1.3.0 1.3.0 NULL +diff 1.3.0 2.0.0 major +diff 1.3.0 1.0.0-alpha major +diff 1.3.0 1.0.0-alpha.1 major +diff 1.3.0 1.0.0-alpha.beta major +diff 1.3.0 1.0.0-beta major +diff 1.3.0 1.0.0-beta.2 major +diff 1.3.0 1.0.0-beta.11 major +diff 1.3.0 1.0.0-rc.1 major +diff 1.3.0 1.2.3-0 minor +diff 1.3.0 2.0.0-0 premajor +diff 1.3.0 1.2.3+build minor +diff 1.3.0 1.0.0+007 minor +diff 1.3.0 10.0.0 major +diff 2.0.0 0.0.0 major +diff 2.0.0 1.0.0 major +diff 2.0.0 1.2.3 major +diff 2.0.0 1.2.4 major +diff 2.0.0 1.3.0 major +diff 2.0.0 2.0.0 NULL +diff 2.0.0 1.0.0-alpha major +diff 2.0.0 1.0.0-alpha.1 major +diff 2.0.0 1.0.0-alpha.beta major +diff 2.0.0 1.0.0-beta major +diff 2.0.0 1.0.0-beta.2 major +diff 2.0.0 1.0.0-beta.11 major +diff 2.0.0 1.0.0-rc.1 major +diff 2.0.0 1.2.3-0 major +diff 2.0.0 2.0.0-0 major +diff 2.0.0 1.2.3+build major +diff 2.0.0 1.0.0+007 major +diff 2.0.0 10.0.0 major +diff 1.0.0-alpha 0.0.0 premajor +diff 1.0.0-alpha 1.0.0 major +diff 1.0.0-alpha 1.2.3 major +diff 1.0.0-alpha 1.2.4 major +diff 1.0.0-alpha 1.3.0 major +diff 1.0.0-alpha 2.0.0 major +diff 1.0.0-alpha 1.0.0-alpha NULL +diff 1.0.0-alpha 1.0.0-alpha.1 prerelease +diff 1.0.0-alpha 1.0.0-alpha.beta prerelease +diff 1.0.0-alpha 1.0.0-beta prerelease +diff 1.0.0-alpha 1.0.0-beta.2 prerelease +diff 1.0.0-alpha 1.0.0-beta.11 prerelease +diff 1.0.0-alpha 1.0.0-rc.1 prerelease +diff 1.0.0-alpha 1.2.3-0 preminor +diff 1.0.0-alpha 2.0.0-0 premajor +diff 1.0.0-alpha 1.2.3+build major +diff 1.0.0-alpha 1.0.0+007 major +diff 1.0.0-alpha 10.0.0 major +diff 1.0.0-alpha.1 0.0.0 premajor +diff 1.0.0-alpha.1 1.0.0 major +diff 1.0.0-alpha.1 1.2.3 major +diff 1.0.0-alpha.1 1.2.4 major +diff 1.0.0-alpha.1 1.3.0 major +diff 1.0.0-alpha.1 2.0.0 major +diff 1.0.0-alpha.1 1.0.0-alpha prerelease +diff 1.0.0-alpha.1 1.0.0-alpha.1 NULL +diff 1.0.0-alpha.1 1.0.0-alpha.beta prerelease +diff 1.0.0-alpha.1 1.0.0-beta prerelease +diff 1.0.0-alpha.1 1.0.0-beta.2 prerelease +diff 1.0.0-alpha.1 1.0.0-beta.11 prerelease +diff 1.0.0-alpha.1 1.0.0-rc.1 prerelease +diff 1.0.0-alpha.1 1.2.3-0 preminor +diff 1.0.0-alpha.1 2.0.0-0 premajor +diff 1.0.0-alpha.1 1.2.3+build major +diff 1.0.0-alpha.1 1.0.0+007 major +diff 1.0.0-alpha.1 10.0.0 major +diff 1.0.0-alpha.beta 0.0.0 premajor +diff 1.0.0-alpha.beta 1.0.0 major +diff 1.0.0-alpha.beta 1.2.3 major +diff 1.0.0-alpha.beta 1.2.4 major +diff 1.0.0-alpha.beta 1.3.0 major +diff 1.0.0-alpha.beta 2.0.0 major +diff 1.0.0-alpha.beta 1.0.0-alpha prerelease +diff 1.0.0-alpha.beta 1.0.0-alpha.1 prerelease +diff 1.0.0-alpha.beta 1.0.0-alpha.beta NULL +diff 1.0.0-alpha.beta 1.0.0-beta prerelease +diff 1.0.0-alpha.beta 1.0.0-beta.2 prerelease +diff 1.0.0-alpha.beta 1.0.0-beta.11 prerelease +diff 1.0.0-alpha.beta 1.0.0-rc.1 prerelease +diff 1.0.0-alpha.beta 1.2.3-0 preminor +diff 1.0.0-alpha.beta 2.0.0-0 premajor +diff 1.0.0-alpha.beta 1.2.3+build major +diff 1.0.0-alpha.beta 1.0.0+007 major +diff 1.0.0-alpha.beta 10.0.0 major +diff 1.0.0-beta 0.0.0 premajor +diff 1.0.0-beta 1.0.0 major +diff 1.0.0-beta 1.2.3 major +diff 1.0.0-beta 1.2.4 major +diff 1.0.0-beta 1.3.0 major +diff 1.0.0-beta 2.0.0 major +diff 1.0.0-beta 1.0.0-alpha prerelease +diff 1.0.0-beta 1.0.0-alpha.1 prerelease +diff 1.0.0-beta 1.0.0-alpha.beta prerelease +diff 1.0.0-beta 1.0.0-beta NULL +diff 1.0.0-beta 1.0.0-beta.2 prerelease +diff 1.0.0-beta 1.0.0-beta.11 prerelease +diff 1.0.0-beta 1.0.0-rc.1 prerelease +diff 1.0.0-beta 1.2.3-0 preminor +diff 1.0.0-beta 2.0.0-0 premajor +diff 1.0.0-beta 1.2.3+build major +diff 1.0.0-beta 1.0.0+007 major +diff 1.0.0-beta 10.0.0 major +diff 1.0.0-beta.2 0.0.0 premajor +diff 1.0.0-beta.2 1.0.0 major +diff 1.0.0-beta.2 1.2.3 major +diff 1.0.0-beta.2 1.2.4 major +diff 1.0.0-beta.2 1.3.0 major +diff 1.0.0-beta.2 2.0.0 major +diff 1.0.0-beta.2 1.0.0-alpha prerelease +diff 1.0.0-beta.2 1.0.0-alpha.1 prerelease +diff 1.0.0-beta.2 1.0.0-alpha.beta prerelease +diff 1.0.0-beta.2 1.0.0-beta prerelease +diff 1.0.0-beta.2 1.0.0-beta.2 NULL +diff 1.0.0-beta.2 1.0.0-beta.11 prerelease +diff 1.0.0-beta.2 1.0.0-rc.1 prerelease +diff 1.0.0-beta.2 1.2.3-0 preminor +diff 1.0.0-beta.2 2.0.0-0 premajor +diff 1.0.0-beta.2 1.2.3+build major +diff 1.0.0-beta.2 1.0.0+007 major +diff 1.0.0-beta.2 10.0.0 major +diff 1.0.0-beta.11 0.0.0 premajor +diff 1.0.0-beta.11 1.0.0 major +diff 1.0.0-beta.11 1.2.3 major +diff 1.0.0-beta.11 1.2.4 major +diff 1.0.0-beta.11 1.3.0 major +diff 1.0.0-beta.11 2.0.0 major +diff 1.0.0-beta.11 1.0.0-alpha prerelease +diff 1.0.0-beta.11 1.0.0-alpha.1 prerelease +diff 1.0.0-beta.11 1.0.0-alpha.beta prerelease +diff 1.0.0-beta.11 1.0.0-beta prerelease +diff 1.0.0-beta.11 1.0.0-beta.2 prerelease +diff 1.0.0-beta.11 1.0.0-beta.11 NULL +diff 1.0.0-beta.11 1.0.0-rc.1 prerelease +diff 1.0.0-beta.11 1.2.3-0 preminor +diff 1.0.0-beta.11 2.0.0-0 premajor +diff 1.0.0-beta.11 1.2.3+build major +diff 1.0.0-beta.11 1.0.0+007 major +diff 1.0.0-beta.11 10.0.0 major +diff 1.0.0-rc.1 0.0.0 premajor +diff 1.0.0-rc.1 1.0.0 major +diff 1.0.0-rc.1 1.2.3 major +diff 1.0.0-rc.1 1.2.4 major +diff 1.0.0-rc.1 1.3.0 major +diff 1.0.0-rc.1 2.0.0 major +diff 1.0.0-rc.1 1.0.0-alpha prerelease +diff 1.0.0-rc.1 1.0.0-alpha.1 prerelease +diff 1.0.0-rc.1 1.0.0-alpha.beta prerelease +diff 1.0.0-rc.1 1.0.0-beta prerelease +diff 1.0.0-rc.1 1.0.0-beta.2 prerelease +diff 1.0.0-rc.1 1.0.0-beta.11 prerelease +diff 1.0.0-rc.1 1.0.0-rc.1 NULL +diff 1.0.0-rc.1 1.2.3-0 preminor +diff 1.0.0-rc.1 2.0.0-0 premajor +diff 1.0.0-rc.1 1.2.3+build major +diff 1.0.0-rc.1 1.0.0+007 major +diff 1.0.0-rc.1 10.0.0 major +diff 1.2.3-0 0.0.0 premajor +diff 1.2.3-0 1.0.0 preminor +diff 1.2.3-0 1.2.3 patch +diff 1.2.3-0 1.2.4 patch +diff 1.2.3-0 1.3.0 minor +diff 1.2.3-0 2.0.0 major +diff 1.2.3-0 1.0.0-alpha preminor +diff 1.2.3-0 1.0.0-alpha.1 preminor +diff 1.2.3-0 1.0.0-alpha.beta preminor +diff 1.2.3-0 1.0.0-beta preminor +diff 1.2.3-0 1.0.0-beta.2 preminor +diff 1.2.3-0 1.0.0-beta.11 preminor +diff 1.2.3-0 1.0.0-rc.1 preminor +diff 1.2.3-0 1.2.3-0 NULL +diff 1.2.3-0 2.0.0-0 premajor +diff 1.2.3-0 1.2.3+build patch +diff 1.2.3-0 1.0.0+007 preminor +diff 1.2.3-0 10.0.0 major +diff 2.0.0-0 0.0.0 premajor +diff 2.0.0-0 1.0.0 premajor +diff 2.0.0-0 1.2.3 premajor +diff 2.0.0-0 1.2.4 premajor +diff 2.0.0-0 1.3.0 premajor +diff 2.0.0-0 2.0.0 major +diff 2.0.0-0 1.0.0-alpha premajor +diff 2.0.0-0 1.0.0-alpha.1 premajor +diff 2.0.0-0 1.0.0-alpha.beta premajor +diff 2.0.0-0 1.0.0-beta premajor +diff 2.0.0-0 1.0.0-beta.2 premajor +diff 2.0.0-0 1.0.0-beta.11 premajor +diff 2.0.0-0 1.0.0-rc.1 premajor +diff 2.0.0-0 1.2.3-0 premajor +diff 2.0.0-0 2.0.0-0 NULL +diff 2.0.0-0 1.2.3+build premajor +diff 2.0.0-0 1.0.0+007 premajor +diff 2.0.0-0 10.0.0 major +diff 1.2.3+build 0.0.0 major +diff 1.2.3+build 1.0.0 minor +diff 1.2.3+build 1.2.3 NULL +diff 1.2.3+build 1.2.4 patch +diff 1.2.3+build 1.3.0 minor +diff 1.2.3+build 2.0.0 major +diff 1.2.3+build 1.0.0-alpha major +diff 1.2.3+build 1.0.0-alpha.1 major +diff 1.2.3+build 1.0.0-alpha.beta major +diff 1.2.3+build 1.0.0-beta major +diff 1.2.3+build 1.0.0-beta.2 major +diff 1.2.3+build 1.0.0-beta.11 major +diff 1.2.3+build 1.0.0-rc.1 major +diff 1.2.3+build 1.2.3-0 patch +diff 1.2.3+build 2.0.0-0 premajor +diff 1.2.3+build 1.2.3+build NULL +diff 1.2.3+build 1.0.0+007 minor +diff 1.2.3+build 10.0.0 major +diff 1.0.0+007 0.0.0 major +diff 1.0.0+007 1.0.0 NULL +diff 1.0.0+007 1.2.3 minor +diff 1.0.0+007 1.2.4 minor +diff 1.0.0+007 1.3.0 minor +diff 1.0.0+007 2.0.0 major +diff 1.0.0+007 1.0.0-alpha major +diff 1.0.0+007 1.0.0-alpha.1 major +diff 1.0.0+007 1.0.0-alpha.beta major +diff 1.0.0+007 1.0.0-beta major +diff 1.0.0+007 1.0.0-beta.2 major +diff 1.0.0+007 1.0.0-beta.11 major +diff 1.0.0+007 1.0.0-rc.1 major +diff 1.0.0+007 1.2.3-0 preminor +diff 1.0.0+007 2.0.0-0 premajor +diff 1.0.0+007 1.2.3+build minor +diff 1.0.0+007 1.0.0+007 NULL +diff 1.0.0+007 10.0.0 major +diff 10.0.0 0.0.0 major +diff 10.0.0 1.0.0 major +diff 10.0.0 1.2.3 major +diff 10.0.0 1.2.4 major +diff 10.0.0 1.3.0 major +diff 10.0.0 2.0.0 major +diff 10.0.0 1.0.0-alpha major +diff 10.0.0 1.0.0-alpha.1 major +diff 10.0.0 1.0.0-alpha.beta major +diff 10.0.0 1.0.0-beta major +diff 10.0.0 1.0.0-beta.2 major +diff 10.0.0 1.0.0-beta.11 major +diff 10.0.0 1.0.0-rc.1 major +diff 10.0.0 1.2.3-0 major +diff 10.0.0 2.0.0-0 major +diff 10.0.0 1.2.3+build major +diff 10.0.0 1.0.0+007 major +diff 10.0.0 10.0.0 NULL +inc 0.0.0 major 1.0.0 +inc 0.0.0 minor 0.1.0 +inc 0.0.0 patch 0.0.1 +inc 0.0.0 premajor 1.0.0-0 +inc 0.0.0 preminor 0.1.0-0 +inc 0.0.0 prepatch 0.0.1-0 +inc 0.0.0 prerelease 0.0.1-0 +inc 1.0.0 major 2.0.0 +inc 1.0.0 minor 1.1.0 +inc 1.0.0 patch 1.0.1 +inc 1.0.0 premajor 2.0.0-0 +inc 1.0.0 preminor 1.1.0-0 +inc 1.0.0 prepatch 1.0.1-0 +inc 1.0.0 prerelease 1.0.1-0 +inc 1.2.3 major 2.0.0 +inc 1.2.3 minor 1.3.0 +inc 1.2.3 patch 1.2.4 +inc 1.2.3 premajor 2.0.0-0 +inc 1.2.3 preminor 1.3.0-0 +inc 1.2.3 prepatch 1.2.4-0 +inc 1.2.3 prerelease 1.2.4-0 +inc 1.2.4 major 2.0.0 +inc 1.2.4 minor 1.3.0 +inc 1.2.4 patch 1.2.5 +inc 1.2.4 premajor 2.0.0-0 +inc 1.2.4 preminor 1.3.0-0 +inc 1.2.4 prepatch 1.2.5-0 +inc 1.2.4 prerelease 1.2.5-0 +inc 1.3.0 major 2.0.0 +inc 1.3.0 minor 1.4.0 +inc 1.3.0 patch 1.3.1 +inc 1.3.0 premajor 2.0.0-0 +inc 1.3.0 preminor 1.4.0-0 +inc 1.3.0 prepatch 1.3.1-0 +inc 1.3.0 prerelease 1.3.1-0 +inc 2.0.0 major 3.0.0 +inc 2.0.0 minor 2.1.0 +inc 2.0.0 patch 2.0.1 +inc 2.0.0 premajor 3.0.0-0 +inc 2.0.0 preminor 2.1.0-0 +inc 2.0.0 prepatch 2.0.1-0 +inc 2.0.0 prerelease 2.0.1-0 +inc 1.0.0-alpha major 1.0.0 +inc 1.0.0-alpha minor 1.0.0 +inc 1.0.0-alpha patch 1.0.0 +inc 1.0.0-alpha premajor 2.0.0-0 +inc 1.0.0-alpha preminor 1.1.0-0 +inc 1.0.0-alpha prepatch 1.0.1-0 +inc 1.0.0-alpha prerelease 1.0.0-alpha.0 +inc 1.0.0-alpha.1 major 1.0.0 +inc 1.0.0-alpha.1 minor 1.0.0 +inc 1.0.0-alpha.1 patch 1.0.0 +inc 1.0.0-alpha.1 premajor 2.0.0-0 +inc 1.0.0-alpha.1 preminor 1.1.0-0 +inc 1.0.0-alpha.1 prepatch 1.0.1-0 +inc 1.0.0-alpha.1 prerelease 1.0.0-alpha.2 +inc 1.0.0-alpha.beta major 1.0.0 +inc 1.0.0-alpha.beta minor 1.0.0 +inc 1.0.0-alpha.beta patch 1.0.0 +inc 1.0.0-alpha.beta premajor 2.0.0-0 +inc 1.0.0-alpha.beta preminor 1.1.0-0 +inc 1.0.0-alpha.beta prepatch 1.0.1-0 +inc 1.0.0-alpha.beta prerelease 1.0.0-alpha.beta.0 +inc 1.0.0-beta major 1.0.0 +inc 1.0.0-beta minor 1.0.0 +inc 1.0.0-beta patch 1.0.0 +inc 1.0.0-beta premajor 2.0.0-0 +inc 1.0.0-beta preminor 1.1.0-0 +inc 1.0.0-beta prepatch 1.0.1-0 +inc 1.0.0-beta prerelease 1.0.0-beta.0 +inc 1.0.0-beta.2 major 1.0.0 +inc 1.0.0-beta.2 minor 1.0.0 +inc 1.0.0-beta.2 patch 1.0.0 +inc 1.0.0-beta.2 premajor 2.0.0-0 +inc 1.0.0-beta.2 preminor 1.1.0-0 +inc 1.0.0-beta.2 prepatch 1.0.1-0 +inc 1.0.0-beta.2 prerelease 1.0.0-beta.3 +inc 1.0.0-beta.11 major 1.0.0 +inc 1.0.0-beta.11 minor 1.0.0 +inc 1.0.0-beta.11 patch 1.0.0 +inc 1.0.0-beta.11 premajor 2.0.0-0 +inc 1.0.0-beta.11 preminor 1.1.0-0 +inc 1.0.0-beta.11 prepatch 1.0.1-0 +inc 1.0.0-beta.11 prerelease 1.0.0-beta.12 +inc 1.0.0-rc.1 major 1.0.0 +inc 1.0.0-rc.1 minor 1.0.0 +inc 1.0.0-rc.1 patch 1.0.0 +inc 1.0.0-rc.1 premajor 2.0.0-0 +inc 1.0.0-rc.1 preminor 1.1.0-0 +inc 1.0.0-rc.1 prepatch 1.0.1-0 +inc 1.0.0-rc.1 prerelease 1.0.0-rc.2 +inc 1.2.3-0 major 2.0.0 +inc 1.2.3-0 minor 1.3.0 +inc 1.2.3-0 patch 1.2.3 +inc 1.2.3-0 premajor 2.0.0-0 +inc 1.2.3-0 preminor 1.3.0-0 +inc 1.2.3-0 prepatch 1.2.4-0 +inc 1.2.3-0 prerelease 1.2.3-1 +inc 2.0.0-0 major 2.0.0 +inc 2.0.0-0 minor 2.0.0 +inc 2.0.0-0 patch 2.0.0 +inc 2.0.0-0 premajor 3.0.0-0 +inc 2.0.0-0 preminor 2.1.0-0 +inc 2.0.0-0 prepatch 2.0.1-0 +inc 2.0.0-0 prerelease 2.0.0-1 +inc 1.2.3+build major 2.0.0 +inc 1.2.3+build minor 1.3.0 +inc 1.2.3+build patch 1.2.4 +inc 1.2.3+build premajor 2.0.0-0 +inc 1.2.3+build preminor 1.3.0-0 +inc 1.2.3+build prepatch 1.2.4-0 +inc 1.2.3+build prerelease 1.2.4-0 +inc 1.0.0+007 major 2.0.0 +inc 1.0.0+007 minor 1.1.0 +inc 1.0.0+007 patch 1.0.1 +inc 1.0.0+007 premajor 2.0.0-0 +inc 1.0.0+007 preminor 1.1.0-0 +inc 1.0.0+007 prepatch 1.0.1-0 +inc 1.0.0+007 prerelease 1.0.1-0 +inc 10.0.0 major 11.0.0 +inc 10.0.0 minor 10.1.0 +inc 10.0.0 patch 10.0.1 +inc 10.0.0 premajor 11.0.0-0 +inc 10.0.0 preminor 10.1.0-0 +inc 10.0.0 prepatch 10.0.1-0 +inc 10.0.0 prerelease 10.0.1-0 +coerce v1.2 1.2.0 +coerce app-1.2.3.jar 1.2.3 +coerce release 4 4.0.0 +coerce 1.2.3.4.5 1.2.3 +coerce .2.3 2.3.0 +coerce 1.2.3-alpha 1.2.3 +coerce =1.2.3 1.2.3 +coerce 10.0.0.0 10.0.0 +coerce 1.2.3.4 1.2.3 +coerce a1b2c3 1.0.0 +coerce 01.02.03 NULL +coerce 1.2 1.2.0 +coerce 123456789012345678.5.6 5.6.0 +coerce 99999999999999999999.0.0 0.0.0 +coerce version 2.3 2.3.0 +coerce x.y.z NULL +coerce 2 2.0.0 +coerce 1.2.x 1.2.0 +coerce 1.0.0 1.0.0 +coerce v0.0.1 0.0.1 +coerce =v1.1.1 1.1.1 +coerce 1.2.3.4.5.6 1.2.3 From 0bf76a59956a75e377825ebe4ae1059c45cb114f Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 13:46:21 +0200 Subject: [PATCH 07/13] ci: update dependabot config --- .github/dependabot.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 377a6ff..3ed6f23 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,10 @@ updates: commit-message: prefix: "ci:" target-branch: "main" + groups: + actions: + patterns: + - "*" - package-ecosystem: "gradle" directory: "/" schedule: @@ -15,12 +19,12 @@ updates: prefix: "build:" target-branch: "main" groups: - "org.flywaydb": - patterns: - - "org.flywaydb*" - "org.jooq": + "minor-and-patch": patterns: - - "org.jooq*" - "net.kyori": + - "*" + update-types: + - "minor" + - "patch" + "major": patterns: - - "net.kyori*" + - "*" From 6a6bdd09e7823f75f017dd02d9522ead1b4600f5 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 13:47:07 +0200 Subject: [PATCH 08/13] build: improve build script and remove deprecated feature --- build.gradle.kts | 10 +++++++--- common/build.gradle.kts | 6 ++++-- gradle.properties | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 14a72dd..04dde99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,8 @@ subprojects { project.version = rootProject.version project.description = rootProject.description + base.archivesName.set("${rootProject.name}-${project.name}") + repositories { mavenCentral() } @@ -35,14 +37,14 @@ subprojects { } java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) + toolchain.languageVersion.set(JavaLanguageVersion.of(25)) withJavadocJar() } tasks { compileJava { options.encoding = Charsets.UTF_8.name() - options.compilerArgs.addAll(arrayListOf("-Xlint:all", "-Xlint:-processing", "-Xdiags:verbose")) + options.compilerArgs.addAll(arrayListOf("-Xlint:all", "-Xlint:-processing", "-Xlint:-options", "-Xdiags:verbose")) options.release.set(8) } @@ -70,7 +72,9 @@ subprojects { fun applyCustomVersion() { // Apply custom version arg or append snapshot version - val ver = properties["altVer"]?.toString() ?: "${rootProject.version}-SNAPSHOT.${Instant.now().epochSecond}" + val ver = providers.gradleProperty("altVer") + .orElse(providers.provider { "${rootProject.version}-SNAPSHOT.${Instant.now().epochSecond}" }) + .get() // Strip prefixed "v" from version tag rootProject.version = (if (ver.first().equals('v', true)) ver.substring(1) else ver.uppercase()).uppercase() diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 3201aab..cdbc88e 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -3,6 +3,7 @@ import com.vanniktech.maven.publish.JavadocJar plugins { alias(libs.plugins.publisher) + signing } mavenPublishing { @@ -56,7 +57,8 @@ mavenPublishing { // Sign all publications signAllPublications() +} - // Skip signing for local tasks - tasks.withType().configureEach { onlyIf { !gradle.taskGraph.allTasks.any { it is PublishToMavenLocal } } } +signing { + isRequired = false // Skip signing if no credentials are provided, e.g. for local publishing } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f56a202..c596976 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xmx2048M group=io.github.milkdrinkers version=1.0.2 -description=A project providing Semantic Versioning utilities for Java 8+ +description=A project providing node-semvers Semantic Versioning tookit, natively implemented Java for Java 8+. SONATYPE_CLOSE_TIMEOUT_SECONDS=1800 \ No newline at end of file From 39554d293aad8e31454395e049dc15d00f119152 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 13:47:34 +0200 Subject: [PATCH 09/13] chore: bump dependency versions --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ac4c7a..ddb9729 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] [libraries] -annotations = "org.jetbrains:annotations:26.0.2-1" -junit-bom = "org.junit:junit-bom:6.0.2" +annotations = "org.jetbrains:annotations:26.1.0" +junit-bom = "org.junit:junit-bom:6.1.2" junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } junit-platform = { module = "org.junit.platform:junit-platform-launcher" } @@ -10,4 +10,4 @@ junit-platform = { module = "org.junit.platform:junit-platform-launcher" } junit = ["junit-jupiter", "junit-platform"] [plugins] -publisher = "com.vanniktech.maven.publish:0.35.0" +publisher = "com.vanniktech.maven.publish:0.37.0" From 7948e14a2254b1c18f41a7606e701675ff3dc536 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 13:58:14 +0200 Subject: [PATCH 10/13] ci: update runner jdk to 25 --- .github/actions/jdk/action.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/pr_test.yml | 4 ++-- .github/workflows/stage.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/actions/jdk/action.yml b/.github/actions/jdk/action.yml index 1acb43d..84230e9 100644 --- a/.github/actions/jdk/action.yml +++ b/.github/actions/jdk/action.yml @@ -5,7 +5,7 @@ inputs: java-version: description: 'Java version' required: false - default: '21' + default: '25' distribution: description: 'JDK Distribution' required: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3da499..956eb30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ on: java: description: 'Java version' type: number - default: 21 + default: 25 os: description: 'OS' type: choice @@ -53,7 +53,7 @@ jobs: uses: ./.github/workflows/test.yml with: os: ${{ inputs.os || 'ubuntu-latest' }} - java: ${{ inputs.java || 21 }} + java: ${{ inputs.java || 25 }} fail_on_test_failure: true # Build and stage artifacts @@ -63,7 +63,7 @@ jobs: uses: ./.github/workflows/stage.yml with: os: ${{ inputs.os || 'ubuntu-latest' }} - java: ${{ inputs.java || 21 }} + java: ${{ inputs.java || 25 }} generate_attestations: ${{ inputs.generate_attestations || true }} retention_days: ${{ inputs.retention_days || 7 }} prerelease_suffix: ${{ inputs.prerelease_suffix || '-RC' }} diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index eacfb7a..b370427 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -9,7 +9,7 @@ on: java: description: 'Java version' type: number - default: 21 + default: 25 os: description: 'OS' type: choice @@ -29,5 +29,5 @@ jobs: uses: ./.github/workflows/test.yml with: os: ${{ inputs.os || 'ubuntu-latest' }} - java: ${{ inputs.java || 21 }} + java: ${{ inputs.java || 25 }} fail_on_test_failure: true diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml index 2872dc7..4b97f0f 100644 --- a/.github/workflows/stage.yml +++ b/.github/workflows/stage.yml @@ -12,7 +12,7 @@ on: description: "The Java version the workflow should use" required: false type: number - default: 21 + default: 25 retention_days: description: "Artifact retention days (default: 7)" required: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b80192c..549c790 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: description: "The Java version the workflow should use" required: false type: number - default: 21 + default: 25 retention_days: description: "Artifact retention days (default: 7)" required: false From 852dfc25d8630ba3945c293530e3b862a3baff44 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 15:37:23 +0200 Subject: [PATCH 11/13] chore: bump gradle wrapper to 9.7.0 again --- gradle/wrapper/gradle-wrapper.jar | Bin 48462 -> 47505 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b1b8ef56b44f16b14dc800fa8103a6d89abb526f..eddabd2eef8d94a5437d6168ff9c87a78ff725b3 100644 GIT binary patch delta 39079 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 F{Rc_uT_ykk delta 40101 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 From 5a7484985859b42f850a0949f97ecdc44e4eeee6 Mon Sep 17 00:00:00 2001 From: darksaid98 Date: Mon, 10 Aug 2026 15:42:39 +0200 Subject: [PATCH 12/13] docs: update readme to include new info --- docs/README.md | 44 ++++++++++++++++++++++++++++++-------------- docs/image.png | Bin 0 -> 75909 bytes 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 docs/image.png diff --git a/docs/README.md b/docs/README.md index a2f9a61..f5739fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@

    Java-Semver

    - _**Java-Semver** is a lightweight, zero-dependency Java library that provides full support for **Semantic Versioning 2.0.0**. Designed for simplicity and reliability, it enables parsing and comparing semantic versions (`major.minor.patch-preRelease+build`) effortlessly. Perfect for applications requiring precise version management._ + _A modern & lightweight Java library providing full **Semantic Versioning 2.0.0** specification compliance. Designed as a port of the javascript ecosystems [Node Semver](https://github.com/npm/node-semver), it ports all major features of [Node Semver](https://github.com/npm/node-semver) to native Java._
    @@ -32,15 +32,20 @@ --- +![code image](image.png) + ## 🌟 Features -- **Full SemVer 2.0.0 Compliance**: Strict adherence to the official specification. +- **Full SemVer 2.0.0 Compliance**: Adherence to the official specification. - **Zero Dependencies**: Lightweight and self-contained. - **Java 8+ compatible**: Compatible with legacy and modern Java projects. -- **Simple API**: Intuitive methods for parsing and comparing versions. -- **Error Handling**: Gracefully handles invalid versions through exceptions. -- **Well-tested**: Robust JUnit test coverage ensures reliability. -- **Pre-release & Build Metadata**: Supports `1.0.0-alpha+001` and other complex formats. +- **Node Semver Features Ported:** + - Parsing + - Comparisons + - Ranges + - Incrementing + - Coercion +- **Well-tested**: Extensive test coverage, including parity tests with node-semver. ## 📦 Installation @@ -78,19 +83,30 @@ dependencies { -## Usage Example 🚀 +## Usage Example ```java import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.ReleaseType; + +// Parsing and comparisons +final Version current = Version.parse("1.0.0-rc.1+5"); +final Version latest = Version.parse("2.0.0-beta+exp.sha.5114f85"); + +current.isLessThan(latest); // true +current.isGreaterThan(latest); // false +current.isEqualTo(latest); // false + +// Ranges +Version.parse("1.2.3").satisfies("^1.0.0"); // true +Version.parse("2.0.0").satisfies(">=1.0.0 <2.0.0"); // false -final Version currentVersion = Version.of("1.0.0-RC.1+5"); -final Version latestVersion = Version.of("2.0.0-beta+exp.sha.5114f85"); +// Incrementing +Version.parse("1.2.3").increment(ReleaseType.MINOR); // 1.3.0 +Version.parse("1.2.3").nextPatch(); // 1.2.4 -Version.isNewer(currentVersion, latestVersion); // false -Version.isNewerOrEqual(currentVersion, latestVersion); // false -Version.isEqual(currentVersion, latestVersion); // false -Version.isOlderOrEqual(currentVersion, latestVersion); // true -Version.isOlder(currentVersion, latestVersion); // true +// Coerceion +Version.coerce("release-v1.2"); // Optional wrapped 1.2.0 ``` ## 📚 Documentation diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..9816edcbec5ef572ac6d26d0c7811ba5f46e7e94 GIT binary patch literal 75909 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 Date: Mon, 10 Aug 2026 16:45:22 +0200 Subject: [PATCH 13/13] refactor: move builder inside Version class --- .../milkdrinkers/javasemver/Version.java | 125 +++++++++++++++++- .../javasemver/VersionBuilder.java | 106 --------------- 2 files changed, 120 insertions(+), 111 deletions(-) delete mode 100644 common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java index f492e8c..c6c6433 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java @@ -161,7 +161,8 @@ public static Optional clean(String input) { * @param patch the patch version * @return the semantic version * @throws VersionBuildException thrown if parsing the data into a version failed - * @apiNote Uses {@link VersionBuilder} internally + * @apiNote Uses {@link Builder} internally + * @see #builder() */ public static @NotNull Version of(long major, long minor, long patch) throws VersionBuildException { return of(major, minor, patch, ""); @@ -176,7 +177,8 @@ public static Optional clean(String input) { * @param preRelease the pre-release version * @return the semantic version * @throws VersionBuildException thrown if parsing the data into a version failed - * @apiNote Uses {@link VersionBuilder} internally + * @apiNote Uses {@link Builder} internally + * @see #builder() */ public static @NotNull Version of(long major, long minor, long patch, String preRelease) throws VersionBuildException { return of(major, minor, patch, preRelease, ""); @@ -192,10 +194,11 @@ public static Optional clean(String input) { * @param meta the build-meta * @return the semantic version * @throws VersionBuildException thrown if parsing the data into a version failed - * @apiNote Uses {@link VersionBuilder} internally + * @apiNote Uses {@link Builder} internally + * @see #builder() */ public static @NotNull Version of(long major, long minor, long patch, String preRelease, String meta) throws VersionBuildException { - return new VersionBuilder() + return builder() .withMajor(major) .withMinor(minor) .withPatch(patch) @@ -370,7 +373,7 @@ public boolean isSnapshot() { public @NotNull Version increment(@NotNull ReleaseType release, @Nullable String identifier) throws VersionBuildException { Objects.requireNonNull(release, "release type must not be null"); - final VersionBuilder builder = new VersionBuilder(); + final Builder builder = Version.builder(); switch (release) { case MAJOR: @@ -692,4 +695,116 @@ public int hashCode() { public String toString() { return getVersionFull(); } + + /** + * Creates a new {@link Builder} for constructing a {@link Version} object. + * + * @return the version builder + */ + public static @NotNull Builder builder() { + return new Builder(); + } + + /** + * A class allowing the creation of {@link Version} objects through this builder. + * + * @implSpec Major, Minor and Patch are required in a valid semantic version. + */ + public static final class Builder { + private Long major; // The Major version + private Long minor; // The Minor version + private Long patch; // The Patch version + private String preRelease; // The pre-release data like "SNAPSHOT-1" or "RC-3" + private String meta; // The build-metadata + + /** + * Set major version for builder. + * + * @param major the major + * @return the version builder + */ + public Builder withMajor(long major) { + this.major = major; + return this; + } + + /** + * Set minor version for builder. + * + * @param minor the minor + * @return the version builder + */ + public Builder withMinor(long minor) { + this.minor = minor; + return this; + } + + /** + * Set patch version for builder. + * + * @param patch the patch + * @return the version builder + */ + public Builder withPatch(long patch) { + this.patch = patch; + return this; + } + + /** + * Set pre-release version for builder. + * + * @param preRelease the pre-release + * @return the version builder + */ + public Builder withPreRelease(String preRelease) { + this.preRelease = preRelease; + return this; + } + + /** + * Set build-metadata version for builder. + * + * @param meta the build-metadata + * @return the version builder + */ + public Builder withMeta(String meta) { + this.meta = meta; + return this; + } + + /** + * Build version. + * + * @return the {@link Version} + * @throws VersionBuildException thrown if the version could not be built + * @implSpec Major, Minor and Patch are required when building a valid semantic version. + */ + public Version build() throws VersionBuildException { + if (major == null) + throw new VersionBuildException("Major version needs to be specified."); + + if (minor == null) + throw new VersionBuildException("Minor version needs to be specified."); + + if (patch == null) + throw new VersionBuildException("Patch version needs to be specified."); + + if (major < 0L) + throw new VersionBuildException(String.format("Major version \"%s\" can't be less than 0.", major)); + + if (minor < 0L) + throw new VersionBuildException(String.format("Minor version \"%s\" can't be less than 0.", minor)); + + if (patch < 0L) + throw new VersionBuildException(String.format("Patch version \"%s\" can't be less than 0.", patch)); + + if (preRelease == null) + preRelease = ""; + + if (meta == null) + meta = ""; + + return new Version(major, minor, patch, preRelease, meta); + } + } } \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java deleted file mode 100644 index 423f764..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionBuilder.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -import io.github.milkdrinkers.javasemver.exception.VersionBuildException; - -/** - * A class allowing the creation of {@link Version} objects through this builder. - * - * @implSpec Major, Minor and Patch are required in a valid semantic version. - */ -public final class VersionBuilder { - private Long major; // The Major version - private Long minor; // The Minor version - private Long patch; // The Patch version - private String preRelease; // The pre-release data like "SNAPSHOT-1" or "RC-3" - private String meta; // The build-metadata - - /** - * Set major version for builder. - * - * @param major the major - * @return the version builder - */ - public VersionBuilder withMajor(long major) { - this.major = major; - return this; - } - - /** - * Set minor version for builder. - * - * @param minor the minor - * @return the version builder - */ - public VersionBuilder withMinor(long minor) { - this.minor = minor; - return this; - } - - /** - * Set patch version for builder. - * - * @param patch the patch - * @return the version builder - */ - public VersionBuilder withPatch(long patch) { - this.patch = patch; - return this; - } - - /** - * Set pre-release version for builder. - * - * @param preRelease the pre-release - * @return the version builder - */ - public VersionBuilder withPreRelease(String preRelease) { - this.preRelease = preRelease; - return this; - } - - /** - * Set build-metadata version for builder. - * - * @param meta the build-metadata - * @return the version builder - */ - public VersionBuilder withMeta(String meta) { - this.meta = meta; - return this; - } - - /** - * Build version. - * - * @return the {@link Version} - * @throws VersionBuildException thrown if the version could not be built - * @implSpec Major, Minor and Patch are required when building a valid semantic version. - */ - public Version build() throws VersionBuildException { - if (major == null) - throw new VersionBuildException("Major version needs to be specified."); - - if (minor == null) - throw new VersionBuildException("Minor version needs to be specified."); - - if (patch == null) - throw new VersionBuildException("Patch version needs to be specified."); - - if (major < 0L) - throw new VersionBuildException(String.format("Major version \"%s\" can't be less than 0.", major)); - - if (minor < 0L) - throw new VersionBuildException(String.format("Minor version \"%s\" can't be less than 0.", minor)); - - if (patch < 0L) - throw new VersionBuildException(String.format("Patch version \"%s\" can't be less than 0.", patch)); - - if (preRelease == null) - preRelease = ""; - - if (meta == null) - meta = ""; - - return new Version(major, minor, patch, preRelease, meta); - } -} \ No newline at end of file

    + * The versions are grouped into maximal contiguous runs of versions that satisfy + * {@code range}, and each run is collapsed to its tightest primitive form ({@code >=first + * <=last}, or a bare version for a single-element run), joined by {@code ||}. This preserves + * membership for every version in {@code versions}, though it isn't guaranteed to be the + * globally shortest possible string. + * + * @param versions the versions whose membership in {@code range} must be preserved + * @param range the range to simplify + * @return a range equivalent to {@code range} for every version in {@code versions}. If none of the supplied versions satisfy {@code range}, a range that matches nothing + * @apiNote Delegates to {@link RangeAlgebra#simplify(Collection, Range)} + */ + public static @NotNull Range simplify(@NotNull Collection versions, @NotNull Range range) { + return RangeAlgebra.simplify(versions, range); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Range)) return false; + Range range = (Range) o; + return Objects.equals(comparatorSets, range.comparatorSets); + } + + @Override + public int hashCode() { + return Objects.hash(comparatorSets); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < comparatorSets.size(); i++) { + if (i > 0) + sb.append(" || "); + + final List comparatorSet = comparatorSets.get(i); + for (int j = 0; j < comparatorSet.size(); j++) { + if (j > 0) + sb.append(" "); + + sb.append(comparatorSet.get(j)); + } + } + + return sb.toString(); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java b/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java index 0dad536..f492e8c 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/Version.java @@ -1,38 +1,44 @@ package io.github.milkdrinkers.javasemver; +import io.github.milkdrinkers.javasemver.enums.ReleaseType; import io.github.milkdrinkers.javasemver.exception.VersionBuildException; import io.github.milkdrinkers.javasemver.exception.VersionParseException; +import io.github.milkdrinkers.javasemver.internal.Coercion; +import io.github.milkdrinkers.javasemver.internal.RangeAlgebra; +import io.github.milkdrinkers.javasemver.internal.VersionComparison; +import io.github.milkdrinkers.javasemver.internal.VersionParsing; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; +import java.util.*; /** - * A class representing a Semantic Version. + * An immutable value object representing a Semantic Version, made up of a major, minor and + * patch component and optional pre-release and build-metadata. Implements {@link Comparable} for precedence ordering, + * and offers parsing/coercion factories along with comparison, increment and range operations. */ -public class Version extends VersionCompare implements Comparable { - // Base fields - private final long major; // The Major version - private final long minor; // The Minor version - private final long patch; // The Patch version - private final String preRelease; // The pre-release data like "SNAPSHOT-1" or "RC-3" - private final String[] preReleaseIdentifiers; // The pre-release data divided into strings by the seperator "." - private final String meta; // The build-metadata - - // Cached fields +public class Version implements Comparable { + + // Base + private final long major; + private final long minor; + private final long patch; + private final String preRelease; // pre-release data like "SNAPSHOT-1" or "RC-3" + private final List preReleaseIdentifiers; // pre-release data divided into strings by the seperator "." + private final String meta; // build-metadata + + // Cached private final boolean hasPreRelease; - private final boolean hasMeta; - private final String version; // The version consisting of only Major.Minor.Patch - private final String versionFull; // The entire version string + private final boolean hasBuildMetadata; + private final String version; // version consisting of only Major.Minor.Patch + private final String versionFull; // entire version string - // Cached fields - private final boolean isAlpha; // Whether the a pre-release contains "alpha" - private final boolean isBeta; // Whether the a pre-release contains "beta" - private final boolean isDevelopment; // Whether the a pre-release contains "dev", "develop" or "development" - private final boolean isReleaseCandidate; // Whether the a pre-release contains "rc" - private final boolean isSnapshot; // Whether the a pre-release contains "snapshot" + private final boolean isAlpha; // whether the pre-release contains "alpha" + private final boolean isBeta; // whether the pre-release contains "beta" + private final boolean isDevelopment; // whether the pre-release contains "dev", "develop" or "development" + private final boolean isReleaseCandidate; // whether the pre-release contains "rc" + private final boolean isSnapshot; // whether the pre-release contains "snapshot" /** * Instantiates a new version object. @@ -46,22 +52,25 @@ public class Version extends VersionCompare implements Comparable { */ @ApiStatus.Internal Version(long major, long minor, long patch, String preRelease, String meta) { - // Base fields + // Base this.major = major; this.minor = minor; this.patch = patch; this.preRelease = preRelease; - this.preReleaseIdentifiers = Arrays.stream(preRelease.split("\\.")) - .filter(string -> !string.isEmpty()) // Fixes bug where empty strings would count as identifiers - .toArray(String[]::new); + final List ids = new ArrayList<>(); + for (String s : preRelease.split("\\.")) { + if (!s.isEmpty()) // or empty strings may count as identifiers + ids.add(s); + } + this.preReleaseIdentifiers = Collections.unmodifiableList(ids); this.meta = meta; this.hasPreRelease = !getPreRelease().isEmpty(); - this.hasMeta = !getBuildMetadata().isEmpty(); + this.hasBuildMetadata = !getBuildMetadata().isEmpty(); this.version = concatenateVersionString(this.major, this.minor, this.patch); this.versionFull = concatenateVersionStringFull(this.major, this.minor, this.patch, this.preRelease, this.meta); - // Cached fields + // Cached this.isAlpha = preRelease.toLowerCase().contains("alpha"); this.isBeta = preRelease.toLowerCase().contains("beta"); this.isDevelopment = preRelease.toLowerCase().contains("dev") || preRelease.toLowerCase().contains("develop") || preRelease.toLowerCase().contains("development"); @@ -75,10 +84,10 @@ public class Version extends VersionCompare implements Comparable { * @param version a string containing a semantic version * @return a version object * @throws VersionParseException thrown if parsing the string into a version failed - * @apiNote Uses {@link VersionParser#parse(String)} internally + * @apiNote Uses {@link VersionParsing#parseStrict(String)} internally */ - public static @NotNull Version of(String version) throws VersionParseException { - return VersionParser.parse(version); + public static @NotNull Version parse(String version) throws VersionParseException { + return VersionParsing.parseStrict(version); } /** @@ -86,16 +95,64 @@ public class Version extends VersionCompare implements Comparable { * * @param version a string containing a semantic version * @return a version object wrapped in a optional - * @apiNote Uses {@link VersionParser#parse(String)} internally + * @apiNote Uses {@link VersionParsing#parseStrict(String)} internally */ - public static @NotNull Optional ofOptional(String version) { + public static @NotNull Optional parseOptional(String version) { try { - return Optional.of(VersionParser.parse(version)); - } catch (Exception ignored) { + return Optional.of(VersionParsing.parseStrict(version)); + } catch (RuntimeException ignored) { return Optional.empty(); } } + /** + * Create a Version object from a version string, tolerating a leading {@code v}/{@code V}, a leading {@code =}, surrounding whitespace, and leading zeros in numeric components. + * + * @param version a string containing a loosely formatted semantic version + * @return a version object + * @throws VersionParseException thrown if parsing the string into a version failed + * @apiNote Uses {@link VersionParsing#parseLoose(String)} internally + */ + public static @NotNull Version parseLoose(String version) throws VersionParseException { + return VersionParsing.parseLoose(version); + } + + /** + * Checks whether a version string is a valid semantic version. + * + * @param version a string containing a semantic version + * @return whether the string could be parsed into a version + * @apiNote Uses {@link Version#parseOptional(String)} internally + */ + public static boolean isValid(String version) { + return parseOptional(version).isPresent(); + } + + /** + * Scans an arbitrary string for the first thing that looks like a version and coerces it + * into a Version, defaulting any missing minor/patch component to {@code 0} and discarding + * any trailing pre-release/build-metadata data. + * + * @param input the string to scan + * @return the coerced version wrapped in an optional, or an empty optional if no version like data was found + * @apiNote Uses {@link Coercion#coerce(String)} internally + */ + public static Optional coerce(String input) { + return Coercion.coerce(input); + } + + /** + * Normalizes a loosely formatted version string into a strict semantic version string, + * tolerating surrounding whitespace, a leading {@code =} and a leading {@code v}/{@code V}. + * + * @param input the string to clean + * @return the cleaned version string wrapped in an optional, or an empty optional if the remainder was not a valid version + * @apiNote Uses {@link Coercion#clean(String)} internally + */ + public static Optional clean(String input) { + return Coercion.clean(input); + } + /** * Create a Version object from semantic version data. * @@ -157,7 +214,7 @@ public long getMajor() { } /** - * Gets minor minor. + * Gets the minor version. * * @return the minor */ @@ -179,16 +236,16 @@ public long getPatch() { * * @return the pre release */ - public String getPreRelease() { + public @NotNull String getPreRelease() { return preRelease; } /** * Gets pre-release identifiers. * - * @return the array of identifiers + * @return the unmodifiable list of identifiers */ - public String[] getPreReleaseIdentifiers() { + public @NotNull List getPreReleaseIdentifiers() { return preReleaseIdentifiers; } @@ -197,7 +254,7 @@ public String[] getPreReleaseIdentifiers() { * * @return the metadata */ - public String getBuildMetadata() { + public @NotNull String getBuildMetadata() { return meta; } @@ -215,8 +272,8 @@ public boolean hasPreRelease() { * * @return boolean */ - public boolean hasMeta() { - return hasMeta; + public boolean hasBuildMetadata() { + return hasBuildMetadata; } /** @@ -287,6 +344,153 @@ public boolean isSnapshot() { return isSnapshot; } + /** + * Computes the next version for a given release type, using a default numeric + * pre-release identifier base of {@code 0} where a pre-release is introduced. + * + * @param release the type of increment to perform + * @return the incremented version + * @throws VersionBuildException thrown if the resulting version could not be built + * @apiNote Build metadata is never carried over to the result. + * @see #increment(ReleaseType, String) + */ + public @NotNull Version increment(@NotNull ReleaseType release) throws VersionBuildException { + return increment(release, null); + } + + /** + * Computes the next version for a given release type. + * + * @param release the type of increment to perform + * @param identifier the pre-release identifier to use for {@code PRE*} release types, may be {@code null} (or empty) to fall back to a numeric identifier of {@code 0} + * @return the incremented version + * @throws VersionBuildException thrown if the resulting version could not be built + * @apiNote Build metadata is never carried over to the result. + */ + public @NotNull Version increment(@NotNull ReleaseType release, @Nullable String identifier) throws VersionBuildException { + Objects.requireNonNull(release, "release type must not be null"); + + final VersionBuilder builder = new VersionBuilder(); + + switch (release) { + case MAJOR: + if (hasPreRelease() && minor == 0L && patch == 0L) + return builder.withMajor(major).withMinor(minor).withPatch(patch).build(); + + return builder.withMajor(major + 1L).withMinor(0L).withPatch(0L).build(); + case MINOR: + if (hasPreRelease() && patch == 0L) + return builder.withMajor(major).withMinor(minor).withPatch(patch).build(); + + return builder.withMajor(major).withMinor(minor + 1L).withPatch(0L).build(); + case PATCH: + if (hasPreRelease()) + return builder.withMajor(major).withMinor(minor).withPatch(patch).build(); + + return builder.withMajor(major).withMinor(minor).withPatch(patch + 1L).build(); + case PREMAJOR: + return builder.withMajor(major + 1L).withMinor(0L).withPatch(0L).withPreRelease(nextPreReleaseBase(identifier)).build(); + case PREMINOR: + return builder.withMajor(major).withMinor(minor + 1L).withPatch(0L).withPreRelease(nextPreReleaseBase(identifier)).build(); + case PREPATCH: + return builder.withMajor(major).withMinor(minor).withPatch(patch + 1L).withPreRelease(nextPreReleaseBase(identifier)).build(); + case PRERELEASE: + if (!hasPreRelease()) + return builder.withMajor(major).withMinor(minor).withPatch(patch + 1L).withPreRelease(nextPreReleaseBase(identifier)).build(); + + return builder.withMajor(major).withMinor(minor).withPatch(patch).withPreRelease(nextPreReleaseIdentifier(identifier)).build(); + default: + throw new VersionBuildException("Unsupported release type: " + release); + } + } + + /** + * Shortcut for {@code increment(ReleaseType.MAJOR)}. + * + * @return the next major version + * @throws VersionBuildException thrown if the resulting version could not be built + */ + public @NotNull Version nextMajor() throws VersionBuildException { + return increment(ReleaseType.MAJOR); + } + + /** + * Shortcut for {@code increment(ReleaseType.MINOR)}. + * + * @return the next minor version + * @throws VersionBuildException thrown if the resulting version could not be built + */ + public @NotNull Version nextMinor() throws VersionBuildException { + return increment(ReleaseType.MINOR); + } + + /** + * Shortcut for {@code increment(ReleaseType.PATCH)}. + * + * @return the next patch version + * @throws VersionBuildException thrown if the resulting version could not be built + */ + public @NotNull Version nextPatch() throws VersionBuildException { + return increment(ReleaseType.PATCH); + } + + /** + * Builds the starting pre-release string for a fresh {@code PRE*} increment. + */ + private static String nextPreReleaseBase(String identifier) { + return identifier == null || identifier.isEmpty() ? "0" : identifier + ".0"; + } + + /** + * Computes the next pre-release string for a version that already has one, following the + * {@link ReleaseType#PRERELEASE} rules. An identifier matching the current one bumps its + * trailing numeric part, a different identifier restarts the pre-release, and no identifier + * just bumps the trailing numeric part of whatever is already there. + */ + private String nextPreReleaseIdentifier(String identifier) { + final List ids = getPreReleaseIdentifiers(); + + if (identifier != null && !identifier.isEmpty()) { + if (!ids.isEmpty() && ids.get(0).equals(identifier)) + return String.join(".", bumpTrailingNumericIdentifier(ids)); + + return identifier + ".0"; + } + + return String.join(".", bumpTrailingNumericIdentifier(ids)); + } + + /** + * Increments the trailing identifier if it's numeric, otherwise appends a fresh {@code 0} identifier. + */ + private static List bumpTrailingNumericIdentifier(List identifiers) { + final List bumped = new ArrayList<>(identifiers); + final String last = bumped.isEmpty() ? "" : bumped.get(bumped.size() - 1); + + if (isNumericIdentifier(last)) { + bumped.set(bumped.size() - 1, String.valueOf(Long.parseLong(last) + 1L)); + } else { + bumped.add("0"); + } + + return bumped; + } + + /** + * Checks whether a pre-release identifier consists only of digits. + */ + private static boolean isNumericIdentifier(String identifier) { + if (identifier.isEmpty()) + return false; + + for (int i = 0; i < identifier.length(); i++) { + if (!Character.isDigit(identifier.charAt(i))) + return false; + } + + return true; + } + /** * Concatenates to a Semantic versioning string. */ @@ -302,45 +506,190 @@ private static String concatenateVersionStringFull(long major, long minor, long } /** - * Compares this object with the specified object for order. Returns a + * Compares this object with the specified object for order. Returns a * negative integer, zero, or a positive integer as this object is less * than, equal to, or greater than the specified object. * * @param other the object to be compared. - * @return a negative integer, zero, or a positive integer as this object - * is less than, equal to, or greater than the specified object. + * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. * @throws NullPointerException if the specified object is null - * @throws ClassCastException if the specified object's type prevents it - * from being compared to this object. + * @throws ClassCastException if the specified object's type prevents it from being compared to this object. */ @Override public int compareTo(@NotNull Version other) { - final VersionCheckResult result = VersionCompare.compare(this, other); + return VersionComparison.comparePrecedence(this, other); + } - switch (result) { - case NEWER: - return -1; - case OLDER: - return 1; - default: - return 0; + /** + * Checks whether this version has a higher precedence than the other version. + * + * @param other the version to compare against + * @return true if this version is greater than the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isGreaterThan(Version other) { + return compareTo(other) > 0; + } + + /** + * Checks whether this version has a precedence greater than or equal to the other version. + * + * @param other the version to compare against + * @return true if this version is greater than or equal to the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isAtLeast(Version other) { + return compareTo(other) >= 0; + } + + /** + * Checks whether this version has a lower precedence than the other version. + * + * @param other the version to compare against + * @return true if this version is less than the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isLessThan(Version other) { + return compareTo(other) < 0; + } + + /** + * Checks whether this version has a precedence less than or equal to the other version. + * + * @param other the version to compare against + * @return true if this version is less than or equal to the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. + */ + public boolean isAtMost(Version other) { + return compareTo(other) <= 0; + } + + /** + * Checks whether this version has the same precedence as the other version. + * + * @param other the version to compare against + * @return true if this version is equal in precedence to the other version + * @apiNote Ignores build metadata, matching {@link #compareTo(Version)}. Use {@link #equals(Object)} for strict equality. + */ + public boolean isEqualTo(Version other) { + return compareTo(other) == 0; + } + + /** + * Classifies the kind of change between this version and another version. + * + * @param other the version to compare against + * @return the release type describing the difference, or an empty optional if the two versions have equal precedence + */ + public @NotNull Optional difference(@NotNull Version other) { + final int comparison = compareTo(other); + if (comparison == 0) + return Optional.empty(); + + final Version high = comparison > 0 ? this : other; + final Version low = comparison > 0 ? other : this; + final boolean highHasPre = high.hasPreRelease(); + final boolean lowHasPre = low.hasPreRelease(); + + if (lowHasPre && !highHasPre) { + // a prerelease of a bare major (X.0.0) is always a major level change like: 1.0.0-1 -> 1.0.0 / 1.1.0 / 2.0.0 are all "major" + if (low.getMinor() == 0L && low.getPatch() == 0L) + return Optional.of(ReleaseType.MAJOR); + + // same major.minor.patch. the prerelease was simply dropped, so the change is at the lowest non zero component of the low version + if (low.getMajor() == high.getMajor() && low.getMinor() == high.getMinor() && low.getPatch() == high.getPatch()) { + if (low.getMinor() != 0L && low.getPatch() == 0L) + return Optional.of(ReleaseType.MINOR); + + return Optional.of(ReleaseType.PATCH); + } } + + // pre* prefix only applies when higher version is itself a prerelease + final boolean pre = highHasPre; + + if (getMajor() != other.getMajor()) + return Optional.of(pre ? ReleaseType.PREMAJOR : ReleaseType.MAJOR); + + if (getMinor() != other.getMinor()) + return Optional.of(pre ? ReleaseType.PREMINOR : ReleaseType.MINOR); + + if (getPatch() != other.getPatch()) + return Optional.of(pre ? ReleaseType.PREPATCH : ReleaseType.PATCH); + + return Optional.of(ReleaseType.PRERELEASE); } + /** + * Checks whether this version satisfies a range. + * + * @param range the range to check against + * @return true if this version satisfies the range + * @apiNote Shorthand for {@code range.contains(this)}. + */ + public boolean satisfies(@NotNull Range range) { + return range.contains(this); + } + + /** + * Checks whether this version satisfies a range string. + * + * @param range the range string to check against + * @return true if this version satisfies the range + * @throws io.github.milkdrinkers.javasemver.exception.RangeParseException thrown if the range string could not be parsed + * @apiNote Shorthand for {@code Range.parse(range).contains(this)}. + */ + public boolean satisfies(@NotNull String range) { + return Range.parse(range).contains(this); + } + + /** + * Checks whether this version's precedence is greater than everything a range could possibly match. + * + * @param range the range to check against + * @return true if this version is above every version the range could match + */ + public boolean isAbove(@NotNull Range range) { + return RangeAlgebra.isAbove(this, range); + } + + /** + * Checks whether this versions precedence is less than everything a range could possibly match. + * + * @param range the range to check against + * @return true if this version is below every version the range could match + */ + public boolean isBelow(@NotNull Range range) { + return RangeAlgebra.isBelow(this, range); + } + + /** + * A comparator that orders versions by Semantic Versioning precedence using build metadata as a tiebreak when precedence is otherwise equal. + * + * @apiNote A version with build metadata sorts as greater than an otherwise equal version without any. + */ + public static final Comparator BUILD_AWARE = VersionComparison::compareBuild; + @Override public boolean equals(Object o) { + if (this == o) return true; if (!(o instanceof Version)) return false; Version version = (Version) o; - return getMajor() == version.getMajor() && getMinor() == version.getMinor() && getPatch() == version.getPatch() && Objects.equals(hasPreRelease(), version.hasPreRelease()) && Objects.equals(getBuildMetadata(), version.getBuildMetadata()); + + return getMajor() == version.getMajor() + && getMinor() == version.getMinor() + && getPatch() == version.getPatch() + && Objects.equals(getPreRelease(), version.getPreRelease()) + && Objects.equals(getBuildMetadata(), version.getBuildMetadata()); } @Override public int hashCode() { - return Objects.hash(getMajor(), getMinor(), getPatch(), hasPreRelease(), getBuildMetadata()); + return Objects.hash(getMajor(), getMinor(), getPatch(), getPreRelease(), getBuildMetadata()); } @Override public String toString() { return getVersionFull(); } -} +} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java deleted file mode 100644 index cc62050..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCheckResult.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -/** - * Contains version comparison results. - */ -public enum VersionCheckResult { - /** - * Newer version check result. - */ - NEWER, - /** - * Equal version check result. - */ - EQUAL, - /** - * Older version check result. - */ - OLDER -} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java deleted file mode 100644 index 2547dfa..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionCompare.java +++ /dev/null @@ -1,209 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Contains all methods used for comparing two {@link Version} objects according to the Semantic Versioning 2.0 specification. - */ -public abstract class VersionCompare { - /** - * Takes a result from Java compare methods and returns the equivalent {@link VersionCheckResult}. - * - * @param result any integer - * @return {@link VersionCheckResult#EQUAL} if 0, {@link VersionCheckResult#OLDER} if less than 0, {@link VersionCheckResult#NEWER} if greater than 0. - */ - @ApiStatus.Internal - private static VersionCheckResult result(long result) { - if (result == 0D) - return VersionCheckResult.EQUAL; - - return result < 0D ? VersionCheckResult.OLDER : VersionCheckResult.NEWER; - } - - /** - * Compare major, minor, patch and then pre-release data of two versions. - * - * @param current the current version to compare with - * @param other the other version to compare against - * @return the version check result - * @apiNote Follows Semver spec such that this is always true: {@code 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 < 2.0.0 < 2.1.0 < 2.1.1}. - */ - public static @NotNull VersionCheckResult compare(@NotNull Version current, @NotNull Version other) { - final long majorChange = current.getMajor() - other.getMajor(); - final long minorChange = current.getMinor() - other.getMinor(); - final long patchChange = current.getPatch() - other.getPatch(); - - // If there are any changes in versioning we know whether the version is newer or older - if (majorChange != 0) - return result(majorChange); - - if (minorChange != 0) - return result(minorChange); - - if (patchChange != 0) - return result(patchChange); - - // No change in major, minor or patch, check pre-release identifiers - return comparePreRelease(current, other); - } - - /** - * Compare pre-release data of two versions. - * - * @param current the current version to compare with - * @param other the other version to compare against - * @return the version check result - * @apiNote Follows Semver spec such that this is always true: {@code 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}. - */ - @SuppressWarnings("ConstantValue") - public static @NotNull VersionCheckResult comparePreRelease(@NotNull Version current, @NotNull Version other) { - final String[] currentIdentifiers = current.getPreReleaseIdentifiers(); - final String[] otherIdentifiers = other.getPreReleaseIdentifiers(); - - // If one array is empty and the other isn't, the version with a pre-release has lower precedence - if (currentIdentifiers == null || currentIdentifiers.length == 0) { - if (otherIdentifiers == null || otherIdentifiers.length == 0) { - return VersionCheckResult.EQUAL; - } - return VersionCheckResult.NEWER; // No pre-release has higher precedence - } else if (otherIdentifiers == null || otherIdentifiers.length == 0) { - return VersionCheckResult.OLDER; // identifiers1 has pre-release, identifiers2 doesn't - } - - // Compare each identifier in sequence - final int minLength = Math.min(currentIdentifiers.length, otherIdentifiers.length); - - for (int i = 0; i < minLength; i++) { - final String currentId = currentIdentifiers[i]; - final String otherId = otherIdentifiers[i]; - - // Check if both identifiers are numeric - final boolean isCurrentNumeric = isNumeric(currentId); - final boolean isOtherNumeric = isNumeric(otherId); - - // Rule 3: Numeric identifiers have lower precedence than non-numeric identifiers - if (isCurrentNumeric && !isOtherNumeric) { - return VersionCheckResult.OLDER; - } else if (!isCurrentNumeric && isOtherNumeric) { - return VersionCheckResult.NEWER; - } else if (isCurrentNumeric && isOtherNumeric) { - // Rule 1: Numeric comparison for numeric identifiers - int num1 = Integer.parseInt(currentId); - int num2 = Integer.parseInt(otherId); - - if (num1 < num2) { - return VersionCheckResult.OLDER; - } else if (num1 > num2) { - return VersionCheckResult.NEWER; - } - // Equal, continue iter to next id - } else { - // Rule 2: Lexical comparison for non-numeric identifiers - int comparison = currentId.compareTo(otherId); - - if (comparison < 0) { - return VersionCheckResult.OLDER; - } else if (comparison > 0) { - return VersionCheckResult.NEWER; - } - // Equal, continue iter to next id - } - } - - // Rule 4: If all identifiers so far are equal, the longer array has higher precedence - if (currentIdentifiers.length < otherIdentifiers.length) { - return VersionCheckResult.OLDER; - } else if (currentIdentifiers.length > otherIdentifiers.length) { - return VersionCheckResult.NEWER; - } - - // All identifiers are equal - return VersionCheckResult.EQUAL; - } - - /** - * Checks if a string is a numeric identifier according to SemVer rules. - * A numeric identifier consists of only digits with no leading zeros (except for "0" itself). - * - * @param identifier The identifier to check - * @return true if the identifier is numeric, false otherwise - * @apiNote Follows Semver spec such that this is always true: {@code 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}. - */ - @SuppressWarnings("RedundantIfStatement") - private static boolean isNumeric(@Nullable String identifier) { - if (identifier == null || identifier.isEmpty()) - return false; - - // Check if the string consists of only digits - if (!identifier.matches("\\d+")) - return false; - - // Check for leading zeros (but "0" by itself is fine) - if (identifier.length() > 1 && identifier.startsWith("0")) - return false; - - return true; - } - - /** - * Check if two versions are the same. - * - * @param current the current version - * @param other the other version - * @return true if current version is same as other version - */ - @SuppressWarnings("unused") - public static boolean isEqual(@NotNull Version current, @NotNull Version other) { - return compare(current, other).equals(VersionCheckResult.EQUAL); - } - - /** - * Check if one version is newer. - * - * @param current the current version - * @param other the other version - * @return true if current version is newer than other version - */ - @SuppressWarnings("unused") - public static boolean isNewer(@NotNull Version current, @NotNull Version other) { - return compare(current, other).equals(VersionCheckResult.NEWER); - } - - /** - * Check if one version is older. - * - * @param current the current version - * @param other the other version - * @return true if current version is older than other version - */ - @SuppressWarnings("unused") - public static boolean isOlder(@NotNull Version current, @NotNull Version other) { - return compare(current, other).equals(VersionCheckResult.OLDER); - } - - /** - * Check if one version is newer or same. - * - * @param current the current version - * @param other the other version - * @return true if current version is newer or equal to other version - */ - @SuppressWarnings("unused") - public static boolean isNewerOrEqual(@NotNull Version current, @NotNull Version other) { - return isNewer(current, other) || isEqual(current, other); - } - - /** - * Check if one version is older or same. - * - * @param current the current version - * @param other the other version - * @return true if current version is older or equal to other version - */ - @SuppressWarnings("unused") - public static boolean isOlderOrEqual(@NotNull Version current, @NotNull Version other) { - return isOlder(current, other) || isEqual(current, other); - } -} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java b/common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java deleted file mode 100644 index 4af71fa..0000000 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/VersionParser.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.milkdrinkers.javasemver; - -import io.github.milkdrinkers.javasemver.exception.VersionBuildException; -import io.github.milkdrinkers.javasemver.exception.VersionParseException; - -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Contains parsing logic for {@link Version} objects. - */ -public abstract class VersionParser { - private final static Pattern SEMVER_REGEX = Pattern.compile("^(?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-]+)*))?$"); // Extract named fields for semantic version - - /** - * Parse a {@link String} into a {@link Version}. - * - * @param unparsedVersion the unparsed version string - * @return the resulting {@link Version} - * @throws VersionParseException thrown if a valid semantic version could not be parsed from the string - * @apiNote Uses {@link VersionBuilder} internally - * @implNote Preceding "V" or "v" characters are stripped from the unparsedVersion - */ - public static Version parse(String unparsedVersion) throws VersionParseException { - // Check if version meets minimal requirements (if it doesn't have 4+ characters it can't be valid semver) - if (unparsedVersion.length() < 5) - throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", unparsedVersion)); - - // Strip leading "V" before version - if (unparsedVersion.toUpperCase().startsWith("V")) - unparsedVersion = unparsedVersion.substring(1); - - try { - // Grab version details from string - final Matcher matcher = SEMVER_REGEX.matcher(unparsedVersion); - if (!matcher.matches()) - throw new VersionParseException(String.format("Version could not be parsed from version string \"%s\".", unparsedVersion)); - - // Grap values from matcher groups and put in optionals - final Optional major = Optional.ofNullable(matcher.group("major")).map(Long::parseLong); - final Optional minor = Optional.ofNullable(matcher.group("minor")).map(Long::parseLong); - final Optional patch = Optional.ofNullable(matcher.group("patch")).map(Long::parseLong); - final Optional preRelease = Optional.ofNullable(matcher.group("prerelease")); - final Optional meta = Optional.ofNullable(matcher.group("meta")); - - // Check for missing data - if (!major.isPresent()) - throw new VersionParseException(String.format("Major version could not be parsed from version string \"%s\" when constructing Version object.", unparsedVersion)); - - if (!minor.isPresent()) - throw new VersionParseException(String.format("Minor version could not be parsed from version string \"%s\" when constructing Version object.", unparsedVersion)); - - if (!patch.isPresent()) - throw new VersionParseException(String.format("Patch version could not be parsed from version string \"%s\" when constructing Version object.", unparsedVersion)); - - return new VersionBuilder() - .withMajor(major.get()) - .withMinor(minor.get()) - .withPatch(patch.get()) - .withPreRelease(preRelease.orElse("")) - .withMeta(meta.orElse("")) - .build(); - } catch (IllegalStateException e) { - throw new VersionParseException(String.format("Match operation failed parsing version from string \"%s\" when constructing Version object.", unparsedVersion)); - } catch (IllegalArgumentException e) { - throw new VersionParseException(String.format("Regex groups were not found while parsing version from string \"%s\" when constructing Version object.", unparsedVersion)); - } catch (VersionBuildException e) { - throw new VersionParseException(String.format("Builder failed while parsing version from string \"%s\" when constructing Version object.", unparsedVersion), e); - } - } -} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java new file mode 100644 index 0000000..2297305 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/Operator.java @@ -0,0 +1,61 @@ +package io.github.milkdrinkers.javasemver.enums; + +/** + * Represents a comparison operator used by a single semver range comparator e.g. the {@code >=} in {@code >=1.2.3}. + */ +public enum Operator { + /** + * Greater than ({@code >}). + */ + GT(">"), + + /** + * Greater than or equal to ({@code >=}). + */ + GTE(">="), + + /** + * Less than ({@code <}). + */ + LT("<"), + + /** + * Less than or equal to ({@code <=}). + */ + LTE("<="), + + /** + * Equal to ({@code =}). + */ + EQ("="); + + private final String symbol; + + Operator(String symbol) { + this.symbol = symbol; + } + + /** + * Gets the textual symbol representing this operator e.g. {@code ">="}. + * + * @return the symbol + */ + public String getSymbol() { + return symbol; + } + + /** + * Finds the operator matching the given symbol. + * + * @param symbol the symbol to look up e.g. {@code ">="} + * @return the matching operator, or {@code null} if no operator has that symbol + */ + public static Operator fromSymbol(String symbol) { + for (Operator operator : values()) { + if (operator.symbol.equals(symbol)) + return operator; + } + + return null; + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java new file mode 100644 index 0000000..1d697cd --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/enums/ReleaseType.java @@ -0,0 +1,48 @@ +package io.github.milkdrinkers.javasemver.enums; + +/** + * Represents the type of version increment to perform. + */ +public enum ReleaseType { + /** + * Increment the major version number. + * Example: 1.2.3 becomes 2.0.0 + */ + MAJOR, + + /** + * Increment the minor version number. + * Example: 1.2.3 becomes 1.3.0 + */ + MINOR, + + /** + * Increment the patch version number. + * Example: 1.2.3 becomes 1.2.4 + */ + PATCH, + + /** + * Increment to the next major version with pre-release. + * Example: 1.2.3 becomes 2.0.0-0 + */ + PREMAJOR, + + /** + * Increment to the next minor version with pre-release. + * Example: 1.2.3 becomes 1.3.0-0 + */ + PREMINOR, + + /** + * Increment to the next patch version with pre-release. + * Example: 1.2.3 becomes 1.2.4-0 + */ + PREPATCH, + + /** + * Increment the pre-release version. + * Example: 1.2.3-0 becomes 1.2.3-1, 1.2.3 becomes 1.2.4-0 + */ + PRERELEASE +} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java new file mode 100644 index 0000000..78f7c37 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/RangeParseException.java @@ -0,0 +1,27 @@ +package io.github.milkdrinkers.javasemver.exception; + +/** + * Thrown when a version range cannot be parsed. + */ +public class RangeParseException extends VersionException { + private static final long serialVersionUID = 5693929286274031511L; + + /** + * Constructs a new range parse exception with the specified message. + * + * @param message the detail message + */ + public RangeParseException(String message) { + super(message); + } + + /** + * Constructs a new range parse exception with the specified message and cause. + * + * @param message the detail message + * @param cause the cause + */ + public RangeParseException(String message, Exception cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java index ff81a3e..27ba4ab 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionBuildException.java @@ -1,7 +1,7 @@ package io.github.milkdrinkers.javasemver.exception; public class VersionBuildException extends VersionException { - private static final long serialVersionUID = 6995079221117712762L; + private static final long serialVersionUID = 983442101168699796L; public VersionBuildException(String message) { super(message); diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java index b08a876..f5e08be 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionException.java @@ -1,7 +1,7 @@ package io.github.milkdrinkers.javasemver.exception; public class VersionException extends RuntimeException { - private static final long serialVersionUID = 5869428421273062603L; + private static final long serialVersionUID = 2025937154918639292L; public VersionException(String message) { super(message); diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java index d5d2f17..1429979 100644 --- a/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/exception/VersionParseException.java @@ -1,7 +1,7 @@ package io.github.milkdrinkers.javasemver.exception; public class VersionParseException extends VersionException { - private static final long serialVersionUID = 3454425882326722240L; + private static final long serialVersionUID = 5172097489090152114L; public VersionParseException(String message) { super(message); diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java new file mode 100644 index 0000000..be7901e --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Coercion.java @@ -0,0 +1,60 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Version; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * {@code Coerce}/{@code Clean} logic used by {@link Version}. + */ +@ApiStatus.Internal +public final class Coercion { + private static final Pattern COERCE_REGEX = Pattern.compile("(^|[^\\d])(\\d{1,16})(?:\\.(\\d{1,16}))?(?:\\.(\\d{1,16}))?(?:$|[^\\d])"); // up to three digit groups of at most 16 digits each, each delimited by a non digit or a string boundary (so over long numbers are skipped) + + private Coercion() { + } + + /** + * Scans an arbitrary string for the first thing that looks like a version and coerces it + * into a {@link Version}, defaulting any missing minor/patch component to {@code 0} and + * discarding any trailing pre release/build metadata data. + * + * @param input the string to scan + * @return the coerced version, or {@link Optional#empty()} if no version like data was found + * @apiNote The extracted {@code major.minor.patch} is parsed strictly, so an extracted component with leading zeros (e.g. {@code 01.02.03}) yields an empty result, and each component is capped at 16 digits. + */ + public static @NotNull Optional coerce(String input) { + if (input == null) + return Optional.empty(); + + final Matcher m = COERCE_REGEX.matcher(input); + if (!m.find()) + return Optional.empty(); + + final String major = m.group(2); + final String minor = m.group(3) != null ? m.group(3) : "0"; + final String patch = m.group(4) != null ? m.group(4) : "0"; + + return Version.parseOptional(major + "." + minor + "." + patch); + } + + /** + * Normalizes a loosely formatted version string into a strict semantic version string, tolerating surrounding whitespace, a leading {@code =} and a leading {@code v}/{@code V}. + * + * @param input the string to clean + * @return the cleaned version string, or {@link Optional#empty()} if the remainder was not a valid version + */ + public static @NotNull Optional clean(String input) { + if (input == null) + return Optional.empty(); + + // trip leading run of =/v characters + final String s = input.trim().replaceFirst("^[=vV]+", ""); + + return Version.parseOptional(s).map(Version::toString); + } +} diff --git a/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java new file mode 100644 index 0000000..66b6a58 --- /dev/null +++ b/common/src/main/java/io/github/milkdrinkers/javasemver/internal/Interval.java @@ -0,0 +1,250 @@ +package io.github.milkdrinkers.javasemver.internal; + +import io.github.milkdrinkers.javasemver.Constraint; +import io.github.milkdrinkers.javasemver.Version; +import io.github.milkdrinkers.javasemver.enums.Operator; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Reduces one AND-set of {@link Constraint}s (e.g. the constraints parsed from a single OR-part of + * a range, such as {@code >=1.2.0 <2.0.0}) to a single interval over the version total order. + * + *