Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle prereleases and big numbers #37

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Semver sem1 = new Semver("1.2.3-beta.4+sha899d8g79f87"); // Defaults to STRICT m
Semver sem2 = new Semver("1.2.3-beta.4+sha899d8g79f87", SemverType.NPM); // Specify the mode
```

If the version is invalid, a `SemverException` will be thrown.
If the version is invalid, a `SemverException` will be thrown.
You can access the different parts of the version using `getMajor()`, `getMinor()`, `getPatch()`, `getSuffixTokens()` or `getBuild()`.

| Type | Mandatory | Optional |
Expand Down Expand Up @@ -130,6 +130,7 @@ sem.diff("1.2.3-beta.4+sha32iddfu987"); // BUILD

If you want to check if a version satisfies a requirement, use the `satisfies` method.

- A `SemverException` is thrown if the version is not fully-qualified (containing major, minor, and patch versions).
- In `STRICT` and `LOOSE` modes, the requirement can only be another version.
- In `NPM` mode, the requirement follows [NPM versioning rules](https://github.com/npm/node-semver).

Expand Down Expand Up @@ -180,5 +181,5 @@ You can also use built-in versioning methods such as:

## Contributing

Any pull request or bug report is welcome!
Any pull request or bug report is welcome!
If you have any suggestion about new features, you can open an issue.
37 changes: 30 additions & 7 deletions src/main/java/com/vdurmont/semver4j/Requirement.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,8 @@ private static Requirement evaluateReversePolishNotation(Iterator<Tokenizer.Toke
if (token.type == Tokenizer.TokenType.VERSION) {
if ("*".equals(token.value) || (type == Semver.SemverType.NPM && "latest".equals(token.value))) {
// Special case for "*" and "latest" in NPM
return new Requirement(new Range("0.0.0", Range.RangeOperator.GTE), null, null, null);
Range range = new Range(new Semver("0.0.0", type), Range.RangeOperator.GTE);
return new Requirement(range, null, null, null);
}
Semver version = new Semver(token.value, type);
if (version.getMinor() != null && version.getPatch() != null) {
Expand Down Expand Up @@ -364,7 +365,8 @@ private static Requirement evaluateReversePolishNotation(Iterator<Tokenizer.Toke
throw new SemverException("Invalid requirement");
}

Range range = new Range(token2.value, rangeOp);
Semver version = new Semver(token2.value, type);
Range range = new Range(version, rangeOp);
return new Requirement(range, null, null, null);
} else {
// They don't call it "reverse" for nothing
Expand Down Expand Up @@ -535,6 +537,21 @@ private static Semver extrapolateVersion(Semver semver) {
return new Semver(sb.toString(), semver.getType());
}

/**
* @return Semver type
*/
public Semver.SemverType getType() {
if (this.range != null) {
return this.range.version.getType();
} else if (this.req1 != null) {
return this.req1.getType();
} else if (this.req2 != null) {
return this.req2.getType();
} else {
return Semver.SemverType.STRICT;
}
}

/**
* @see #isSatisfiedBy(Semver)
*
Expand All @@ -543,11 +560,7 @@ private static Semver extrapolateVersion(Semver semver) {
* @return true if the version satisfies the requirement
*/
public boolean isSatisfiedBy(String version) {
if (this.range != null) {
return this.isSatisfiedBy(new Semver(version, this.range.version.getType()));
} else {
return this.isSatisfiedBy(new Semver(version));
}
return this.isSatisfiedBy(new Semver(version, this.getType()));
}

/**
Expand All @@ -558,8 +571,18 @@ public boolean isSatisfiedBy(String version) {
* @return true if the version satisfies the requirement
*/
public boolean isSatisfiedBy(Semver version) {
if (version.getMinor() == null) {
throw new SemverException("Invalid version for requirement check (no minor version): " + version.getValue());
} else if (version.getPatch() == null) {
throw new SemverException("Invalid version for requirement check (no patch version): " + version.getValue());
}

if (this.range != null) {
// We are on a leaf
if (version.isPreRelease() && !this.range.version.isPreRelease()) {
// Disqualify pre-releases if this is not a pre-release range
return false;
}
return this.range.isSatisfiedBy(version);
} else {
// We have several sub-requirements
Expand Down
35 changes: 26 additions & 9 deletions src/main/java/com/vdurmont/semver4j/Semver.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,15 @@ public boolean satisfies(String requirement) {
return this.satisfies(req);
}

/**
* Checks whether the version is a pre-release
*
* @return true if the current version is a prerelease
*/
public boolean isPreRelease() {
return suffixTokens.length > 0;
}

/**
* @see #isGreaterThan(Semver)
*
Expand Down Expand Up @@ -329,8 +338,22 @@ public boolean isEquivalentTo(String version) {
*/
public boolean isEquivalentTo(Semver version) {
// Get versions without build
Semver sem1 = this.getBuild() == null ? this : new Semver(this.getValue().replace("+" + this.getBuild(), ""));
Semver sem2 = version.getBuild() == null ? version : new Semver(version.getValue().replace("+" + version.getBuild(), ""));
Semver sem1 = this.getBuild() == null ? this : new Semver(this.getValue().replace("+" + this.getBuild(), ""), this.getType());
Semver sem2 = version.getBuild() == null ? version : new Semver(version.getValue().replace("+" + version.getBuild(), ""), version.getType());

// Ignore minor and/or patch when versions are not equally qualified
if (sem1.getType() != SemverType.STRICT) {
if (!sem1.areSameSuffixes(sem2.getSuffixTokens())) return false;

if (!Objects.equals(sem1.getMajor(), sem2.getMajor())) return false;

if (sem2.getMinor() == null) return true;
if (!Objects.equals(sem1.getMinor(), sem2.getMinor())) return false;

if (sem2.getPatch() == null) return true;
if (!Objects.equals(sem1.getPatch(), sem2.getPatch())) return false;
}

// Compare those new versions
return sem1.isEqualTo(sem2);
}
Expand All @@ -354,12 +377,6 @@ public boolean isEqualTo(String version) {
* @return true if the current version equals the provided version
*/
public boolean isEqualTo(Semver version) {
if (this.type == SemverType.NPM) {
if (this.getMajor() != version.getMajor()) return false;
if (version.getMinor() == null) return true;
if (version.getPatch() == null) return true;
}

return this.equals(version);
}

Expand Down Expand Up @@ -542,7 +559,7 @@ private static Semver create(SemverType type, int major, Integer minor, Integer

@Override public int compareTo(Semver version) {
if (this.isGreaterThan(version)) return 1;
else if(this.isLowerThan(version)) return -1;
else if (this.isLowerThan(version)) return -1;
return 0;
}

Expand Down
71 changes: 68 additions & 3 deletions src/test/java/com/vdurmont/semver4j/NpmSemverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ public static Iterable<Object[]> getParameters() {
// Fully-qualified versions:
{ "1.2.3", "1.2.3", true, },
{ "1.2.4", "1.2.3", false, },
{ "1.3.3", "1.2.3", false, },
{ "2.2.3", "1.2.3", false, },

// Minor versions:
{ "1.2.3", "1.2", true, },
{ "1.3.3", "1.2", false, },
{ "1.2.4", "1.3", false, },

// Major versions:
Expand All @@ -47,7 +50,7 @@ public static Iterable<Object[]> getParameters() {
{ "2.3.0-alpha", "1.2.3 - 2.3.0-beta", true, },
{ "2.3.4", "1.2.3 - 2.3", true, },
{ "2.3.4", "1.2.3 - 2", true, },
{ "4.4", "3.X - 4.X", true, },
{ "4.4.0", "3.X - 4.X", true, },
{ "1.0.0", "1.2.3 - 2.3.4", false, },
{ "3.0.0", "1.2.3 - 2.3.4", false, },
{ "2.4.3", "1.2.3 - 2.3", false, },
Expand All @@ -58,7 +61,7 @@ public static Iterable<Object[]> getParameters() {
{ "3.1.5", "", true, },
{ "3.1.5", "*", true, },
{ "0.0.0", "*", true, },
{ "1.0.0-beta", "*", true, },
{ "1.0.0-beta", "*", false, },
{ "3.1.5-beta", "3.1.x", false, },
{ "3.1.5-beta+exp.sha.5114f85", "3.1.x", false, },
{ "3.1.5+exp.sha.5114f85", "3.1.x", true, },
Expand Down Expand Up @@ -121,6 +124,8 @@ public static Iterable<Object[]> getParameters() {
{ "2.0.1", "=2.0", true, },
{ "2.0.0", "=2", true, },
{ "2.0.1", "=2", true, },
{ "3.0.0", "=2.0", false, },
{ "2.1.0", "=2.0", false, },
{ "2.0.1", "=2.0.0", false, },
{ "1.9.9", "=2.0.0", false, },
{ "1.9.9", "=2.0", false, },
Expand Down Expand Up @@ -176,6 +181,56 @@ public static Iterable<Object[]> getParameters() {
{ "3.0.0", "<=2.0", false, },
{ "3.0.0", "<=2", false, },

// Prerelease versions:
{ "2.0.0-alpha", "=2.0.0-beta", false, },
{ "2.0.0-rc.2", "=2.0.0-rc.2", true, },
{ "2.0.0-rc.2", "=2.0.0-rc.3", false, },
{ "2.0.0-rc.2", "=2.0.0-rc.2.3", false, },
{ "2.0.0-rc.2", "=2.0.0-rc.3.2", false, },
{ "2.0.0-rc.2", "=2.0.0", false, },
{ "2.0.0-rc.2", "=2.0", false, },
{ "2.0.0-rc.2", "=2", false, },

{ "2.0.0-alpha", ">2.0.0-beta", false, },
{ "2.0.0-rc.2", ">2.0.0-rc.1", true, },
{ "2.0.0-rc.2", ">2.0.0-rc.2", false, },
{ "2.0.0-rc.2", ">2.0.0-rc.3", false, },
{ "2.0.0-rc.2", ">2.0.0-rc.2.3", false, },
{ "2.0.0-rc.2", ">2.0.0-rc.3.2", false, },
{ "2.0.0-rc.2", ">2.0.0", false, },
{ "2.0.0-rc.2", ">2.0", false, },
{ "2.0.0-rc.2", ">2", false, },

{ "2.0.0-alpha", "<2.0.0-beta", true, },
{ "2.0.0-rc.2", "<2.0.0-rc.3", true, },
{ "2.0.0-rc.2", "<2.0.0-rc.2.3", true, },
{ "2.0.0-rc.2", "<2.0.0-rc.3.2", true, },
{ "2.0.0-rc.2", "<2.0.0-rc.1", false, },
{ "2.0.0-rc.2", "<2.0.0-rc.2", false, },
{ "2.0.0-rc.2", "<2.0.0", false, },
{ "2.0.0-rc.2", "<2.0", false, },
{ "2.0.0-rc.2", "<2", false, },

{ "2.0.0-alpha", ">=2.0.0-beta", false, },
{ "2.0.0-rc.2", ">=2.0.0-rc.1", true, },
{ "2.0.0-rc.2", ">=2.0.0-rc.2", true, },
{ "2.0.0-rc.2", ">=2.0.0-rc.3", false, },
{ "2.0.0-rc.2", ">=2.0.0-rc.2.3", false, },
{ "2.0.0-rc.2", ">=2.0.0-rc.3.2", false, },
{ "2.0.0-rc.2", ">=2.0.0", false, },
{ "2.0.0-rc.2", ">=2.0", false, },
{ "2.0.0-rc.2", ">=2", false, },

{ "2.0.0-alpha", "<=2.0.0-beta", true, },
{ "2.0.0-rc.2", "<=2.0.0-rc.2", true, },
{ "2.0.0-rc.2", "<=2.0.0-rc.3", true, },
{ "2.0.0-rc.2", "<=2.0.0-rc.2.3", true, },
{ "2.0.0-rc.2", "<=2.0.0-rc.3.2", true, },
{ "2.0.0-rc.2", "<=2.0.0-rc.1", false, },
{ "2.0.0-rc.2", "<=2.0.0", false, },
{ "2.0.0-rc.2", "<=2.0", false, },
{ "2.0.0-rc.2", "<=2", false, },

// AND ranges:
{ "2.0.1", ">2.0.0 <3.0.0", true, },
{ "2.0.1", ">2.0 <3.0", false, },
Expand Down Expand Up @@ -222,12 +277,22 @@ public static Iterable<Object[]> getParameters() {
{ "1.1.0", "1.2 <1.2.8 || >2.0.0", false, },
{ "1.2.9", "1.2 <1.2.8 || >2.0.0", false, },
{ "2.0.0", "1.2 <1.2.8 || >2.0.0", false, },

// Big number equality:
{ "128.0.0", "=128.0.0", true, },
{ "127.127.127", "=127.128", false, },
{ "0.128.0", "=0.128.0", true, },
{ "0.0.128", "=0.0.128", true, },
{ "127.127.127", "=127.127.127", true, },
{ "128.128.128", "=128.128.128", true, },
{ "999.999.999", "=999.999.999", true, },
{ "9999.9999.9999", "=9999.9999.9999", true, },
});
}

@Test
public void test() {
assertEquals(this.version + " , " + this.rangeExpression ,this.expected, new Semver(this.version, SemverType.NPM).satisfies(this.rangeExpression));
assertEquals(this.version + " , " + this.rangeExpression, this.expected, new Semver(this.version, SemverType.NPM).satisfies(this.rangeExpression));
}

}
13 changes: 11 additions & 2 deletions src/test/java/com/vdurmont/semver4j/RequirementTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
Expand Down Expand Up @@ -324,9 +325,9 @@ public class RequirementTest {
@Test public void isSatisfiedBy_with_a_loose_type() {
Requirement req = Requirement.buildLoose("1.3.2");

assertFalse(req.isSatisfiedBy("0.27"));
assertFalse(req.isSatisfiedBy("0.27.0"));
assertTrue(req.isSatisfiedBy("1.3.2"));
assertFalse(req.isSatisfiedBy("1.5"));
assertFalse(req.isSatisfiedBy("1.5.0"));
}

@Test public void isSatisfiedBy_with_a_complex_example() {
Expand Down Expand Up @@ -422,6 +423,14 @@ public class RequirementTest {
assertTrue(req.isSatisfiedBy("0.2.3"));
}

@Test public void isSatisfiedBy_unqualified_exception() {
Requirement req = Requirement.buildNPM("1.0");
try {
req.isSatisfiedBy("1.0");
fail("isSatisfiedBy() did not throw as expected.");
} catch (SemverException e) {}
}

@Test public void tildeRequirement_cocoapods() {
// '~> 0.1.2' Version 0.1.2 and the versions up to 0.2, not including 0.2 and higher
tildeTest("0.1.2", "0.1.2", "0.2.0", Semver.SemverType.COCOAPODS);
Expand Down
31 changes: 31 additions & 0 deletions src/test/java/com/vdurmont/semver4j/SemverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

Expand Down Expand Up @@ -119,6 +120,15 @@ private static void assertIsSemver(Semver semver, String value, Integer major, I
assertEquals(build, semver.getBuild());
}

@Test public void satisfies_unqualified_exception() {
String version = "1.0";
Semver semver = new Semver(version, Semver.SemverType.LOOSE);
try {
semver.satisfies("1.0");
fail("satisfies() did not throw as expected.");
} catch (SemverException e) {}
}

@Test public void statisfies_works_will_all_the_types() {
// Used to prevent bugs when we add a new type
for (Semver.SemverType type : Semver.SemverType.values()) {
Expand Down Expand Up @@ -187,6 +197,27 @@ private static void assertIsSemver(Semver semver, String value, Integer major, I
assertTrue(semver.isEquivalentTo(version2));
}

@Test public void isEquivalentTo_isEqualTo_without_patch() {
Semver semver = new Semver("1.2.3", Semver.SemverType.NPM);
String version2 = "1.2";
assertFalse(semver.isEqualTo(version2));
assertTrue(semver.isEquivalentTo(version2));
}

@Test public void isEqualTo_symmetric() {
Semver semver1 = new Semver("1.2.3", Semver.SemverType.NPM);
Semver semver2 = new Semver("1.2", Semver.SemverType.NPM);
assertFalse(semver1.isEqualTo(semver2));
assertFalse(semver2.isEqualTo(semver1));
}

@Test public void isEquivalentTo_asymmetric() {
Semver semver1 = new Semver("1.2.3", Semver.SemverType.NPM);
Semver semver2 = new Semver("1.2", Semver.SemverType.NPM);
assertTrue(semver1.isEquivalentTo(semver2));
assertFalse(semver2.isEquivalentTo(semver1));
}

@Test public void statisfies_calls_the_requirement() {
Requirement req = mock(Requirement.class);
Semver semver = new Semver("1.2.2");
Expand Down