Implement partial compatibility with node-semver
This commit enhances expressions for greater compatibility with node-semver (https://github.com/npm/node-semver): - Added support for the Caret operator (^) - Added support for the X-Ranges with additional wildcard "x" - Fixed the Tilde ranges to be fully compatible Known issues: - The X-Ranges in the form of short versions are not interpreted correctly if not followed by EOL: while version "1.2.3" does satisfy expression "1", it doesn't satisfy expression "1 | 2".
This commit is contained in:
parent
ea68756f5e
commit
4b6c9edd66
@ -31,8 +31,8 @@ import com.github.zafarkhaja.semver.util.Stream.ElementType;
|
|||||||
import com.github.zafarkhaja.semver.util.UnexpectedElementException;
|
import com.github.zafarkhaja.semver.util.UnexpectedElementException;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
|
|
||||||
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*;
|
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*;
|
||||||
|
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parser for the SemVer Expressions.
|
* A parser for the SemVer Expressions.
|
||||||
@ -152,6 +152,7 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
* <expr> ::= <comparison-expr>
|
* <expr> ::= <comparison-expr>
|
||||||
* | <version-expr>
|
* | <version-expr>
|
||||||
* | <tilde-expr>
|
* | <tilde-expr>
|
||||||
|
* | <caret-expr>
|
||||||
* | <range-expr>
|
* | <range-expr>
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
@ -161,6 +162,8 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
private CompositeExpression parseExpression() {
|
private CompositeExpression parseExpression() {
|
||||||
if (tokens.positiveLookahead(TILDE)) {
|
if (tokens.positiveLookahead(TILDE)) {
|
||||||
return parseTildeExpression();
|
return parseTildeExpression();
|
||||||
|
} if (tokens.positiveLookahead(CARET)) {
|
||||||
|
return parseCaretExpression();
|
||||||
} else if (isVersionExpression()) {
|
} else if (isVersionExpression()) {
|
||||||
return parseVersionExpression();
|
return parseVersionExpression();
|
||||||
} else if (isRangeExpression()) {
|
} else if (isRangeExpression()) {
|
||||||
@ -230,18 +233,61 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
consumeNextToken(TILDE);
|
consumeNextToken(TILDE);
|
||||||
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
if (!tokens.positiveLookahead(DOT)) {
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
return gte(versionFor(major));
|
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
||||||
}
|
}
|
||||||
consumeNextToken(DOT);
|
consumeNextToken(DOT);
|
||||||
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
if (!tokens.positiveLookahead(DOT)) {
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
return gte(versionFor(major, minor)).and(lt(versionFor(major + 1)));
|
return gte(versionFor(major, minor)).and(lt(versionFor(major, minor + 1)));
|
||||||
}
|
}
|
||||||
consumeNextToken(DOT);
|
consumeNextToken(DOT);
|
||||||
int patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
int patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
return gte(versionFor(major, minor, patch)).and(lt(versionFor(major, minor + 1)));
|
return gte(versionFor(major, minor, patch)).and(lt(versionFor(major, minor + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the {@literal <caret-expr>} non-terminal.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@literal
|
||||||
|
* <caret-expr> ::= "^" <version>
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return the expression AST
|
||||||
|
*/
|
||||||
|
private CompositeExpression parseCaretExpression() {
|
||||||
|
consumeNextToken(CARET);
|
||||||
|
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
|
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
||||||
|
}
|
||||||
|
consumeNextToken(DOT);
|
||||||
|
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
|
if (major > 0) {
|
||||||
|
return gte(versionFor(major, minor)).and(lt(versionFor(major + 1, minor)));
|
||||||
|
} else {
|
||||||
|
return gte(versionFor(major, minor)).and(lt(versionFor(major, minor + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
consumeNextToken(DOT);
|
||||||
|
int patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
|
CompositeExpression ltExp;
|
||||||
|
if (major > 0) {
|
||||||
|
ltExp = lt(versionFor(major + 1));
|
||||||
|
} else if (minor > 0) {
|
||||||
|
ltExp = lt(versionFor(major, minor + 1));
|
||||||
|
} else {
|
||||||
|
if (patch > 0) {
|
||||||
|
ltExp = lt(versionFor(major, minor, patch + 1));
|
||||||
|
} else {
|
||||||
|
ltExp = lt(versionFor(major, minor, patch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gte(versionFor(major, minor, patch)).and(ltExp);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the following version terminals are part
|
* Determines if the following version terminals are part
|
||||||
* of the {@literal <version-expr>} non-terminal.
|
* of the {@literal <version-expr>} non-terminal.
|
||||||
@ -251,7 +297,7 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
* {@code false} otherwise
|
* {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
private boolean isVersionExpression() {
|
private boolean isVersionExpression() {
|
||||||
return isVersionFollowedBy(STAR);
|
return isVersionFollowedBy(WILDCARD) || isVersionFollowedBy(EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,24 +305,48 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* {@literal
|
* {@literal
|
||||||
* <version-expr> ::= <major> "." "*"
|
* <version-expr> ::= <wildcard>
|
||||||
* | <major> "." <minor> "." "*"
|
* | <major> "." <wildcard>
|
||||||
|
* | <major> "." <minor> "." <wildcard>
|
||||||
|
*
|
||||||
|
* <wildcard> ::= "*" | "x" | "X"
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @return the expression AST
|
* @return the expression AST
|
||||||
*/
|
*/
|
||||||
private CompositeExpression parseVersionExpression() {
|
private CompositeExpression parseVersionExpression() {
|
||||||
|
if (tokens.positiveLookahead(WILDCARD)) {
|
||||||
|
tokens.consume();
|
||||||
|
return gte(versionFor(0, 0, 0));
|
||||||
|
}
|
||||||
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
consumeNextToken(DOT);
|
if (tokens.positiveLookahead(DOT)) {
|
||||||
if (tokens.positiveLookahead(STAR)) {
|
consumeNextToken(DOT);
|
||||||
|
}
|
||||||
|
if (tokens.positiveLookahead(WILDCARD) || tokens.positiveLookahead(EOL)) {
|
||||||
|
if (tokens.positiveLookahead(WILDCARD)) {
|
||||||
|
tokens.consume();
|
||||||
|
}
|
||||||
|
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
||||||
|
}
|
||||||
|
if (tokens.positiveLookahead(WILDCARD)) {
|
||||||
tokens.consume();
|
tokens.consume();
|
||||||
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
||||||
}
|
}
|
||||||
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
consumeNextToken(DOT);
|
if (tokens.positiveLookahead(DOT)) {
|
||||||
consumeNextToken(STAR);
|
consumeNextToken(DOT);
|
||||||
return gte(versionFor(major, minor)).and(lt(versionFor(major, minor + 1)));
|
}
|
||||||
|
if (tokens.positiveLookahead(WILDCARD) || tokens.positiveLookahead(EOL)) {
|
||||||
|
if (tokens.positiveLookahead(WILDCARD)) {
|
||||||
|
tokens.consume();
|
||||||
|
}
|
||||||
|
return gte(versionFor(major, minor)).and(lt(versionFor(major, minor + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
|
return gte(versionFor(major, minor, patch));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +57,8 @@ class Lexer {
|
|||||||
LESS("<(?!=)"),
|
LESS("<(?!=)"),
|
||||||
LESS_EQUAL("<="),
|
LESS_EQUAL("<="),
|
||||||
TILDE("~"),
|
TILDE("~"),
|
||||||
STAR("\\*"),
|
WILDCARD("[\\*xX]"),
|
||||||
|
CARET("\\^"),
|
||||||
AND("&"),
|
AND("&"),
|
||||||
OR("\\|"),
|
OR("\\|"),
|
||||||
NOT("!(?!=)"),
|
NOT("!(?!=)"),
|
||||||
|
@ -26,7 +26,8 @@ package com.github.zafarkhaja.semver;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.experimental.runners.Enclosed;
|
import org.junit.experimental.runners.Enclosed;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.*;
|
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.gte;
|
||||||
|
import static com.github.zafarkhaja.semver.expr.CompositeExpression.Helper.lt;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -307,13 +308,6 @@ public class VersionTest {
|
|||||||
assertTrue(0 > v1.compareWithBuildsTo(v2));
|
assertTrue(0 > v1.compareWithBuildsTo(v2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldCheckIfVersionSatisfiesExpressionString() {
|
|
||||||
Version v = Version.valueOf("2.0.0-beta");
|
|
||||||
assertTrue(v.satisfies("~1.0"));
|
|
||||||
assertFalse(v.satisfies(">=2.0 & <3.0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCheckIfVersionSatisfiesExpression() {
|
public void shouldCheckIfVersionSatisfiesExpression() {
|
||||||
Version v = Version.valueOf("2.0.0-beta");
|
Version v = Version.valueOf("2.0.0-beta");
|
||||||
|
@ -89,7 +89,7 @@ public class ExpressionParserTest {
|
|||||||
ExpressionParser parser = new ExpressionParser(new Lexer());
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
Expression expr1 = parser.parse("~1");
|
Expression expr1 = parser.parse("~1");
|
||||||
assertTrue(expr1.interpret(Version.valueOf("1.2.3")));
|
assertTrue(expr1.interpret(Version.valueOf("1.2.3")));
|
||||||
assertTrue(expr1.interpret(Version.valueOf("3.2.1")));
|
assertFalse(expr1.interpret(Version.valueOf("3.2.1")));
|
||||||
Expression expr2 = parser.parse("~1.2");
|
Expression expr2 = parser.parse("~1.2");
|
||||||
assertTrue(expr2.interpret(Version.valueOf("1.2.3")));
|
assertTrue(expr2.interpret(Version.valueOf("1.2.3")));
|
||||||
assertFalse(expr2.interpret(Version.valueOf("2.0.0")));
|
assertFalse(expr2.interpret(Version.valueOf("2.0.0")));
|
||||||
@ -201,4 +201,81 @@ public class ExpressionParserTest {
|
|||||||
}
|
}
|
||||||
fail("Should raise error if closing parenthesis is missing");
|
fail("Should raise error if closing parenthesis is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckIfPrereleaseVersionSatisfiesExpression() {
|
||||||
|
Version v = Version.valueOf("2.1.0-beta");
|
||||||
|
assertTrue(v.satisfies("*")); // >=0.0.0
|
||||||
|
assertTrue(v.satisfies("x")); // >=0.0.0
|
||||||
|
assertTrue(v.satisfies("X")); // >=0.0.0
|
||||||
|
assertTrue(v.satisfies("2.*")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.x")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.X")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.0.*")); // >=2.0.0 <2.1.0
|
||||||
|
assertTrue(v.satisfies("2.0.x")); // >=2.0.0 <2.1.0
|
||||||
|
assertTrue(v.satisfies("2.0.X")); // >=2.0.0 <2.1.0
|
||||||
|
assertTrue(v.satisfies("2")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.0")); // >=2.0.0 <2.1.0
|
||||||
|
assertTrue(v.satisfies("2.0.0")); // >=2.0.0
|
||||||
|
assertTrue(v.satisfies("~2")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("~2.0")); // >=2.0.0 <2.1.0
|
||||||
|
assertTrue(v.satisfies("~2.0.0")); // >=2.0.0 <2.1.0
|
||||||
|
assertTrue(v.satisfies(">=2.0 & <3.0")); // >=2.0.0 & <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.0.0 - 2.1.0")); // >=2.0.0 & <=2.1.0
|
||||||
|
assertTrue(v.satisfies("2.0 - 2.1")); // >=2.0.0 & <=2.1.0
|
||||||
|
assertTrue(v.satisfies("^2")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("^2.0.3")); // >=2.0.3 <3.0.0
|
||||||
|
assertFalse(v.satisfies("2.1.*")); // >=2.1.0 <2.2.0
|
||||||
|
assertFalse(v.satisfies("2.1.x")); // >=2.1.0 <2.2.0
|
||||||
|
assertFalse(v.satisfies("2.1.X")); // >=2.1.0 <2.2.0
|
||||||
|
assertFalse(v.satisfies("~1")); // >=1.0.0 <2.0.0
|
||||||
|
assertFalse(v.satisfies("~1.2.3")); // >=1.2.3 <1.3.0
|
||||||
|
assertFalse(v.satisfies("~1.2")); // >=1.2.0 <1.3.0
|
||||||
|
assertFalse(v.satisfies("~2.2.3")); // >=2.2.3 <2.3.0
|
||||||
|
assertFalse(v.satisfies("^0.2.3")); // >=0.2.3 <0.3.0
|
||||||
|
assertFalse(v.satisfies("^0.0.3")); // >=0.0.3 <0.0.4
|
||||||
|
assertFalse(v.satisfies("^0")); // >=0.0.0 <0.1.0
|
||||||
|
assertFalse(v.satisfies("^0.0")); // >=0.0.0 <0.1.0
|
||||||
|
assertFalse(v.satisfies("^0.0.0")); // >=0.0.0 <0.0.0
|
||||||
|
assertFalse(v.satisfies(">=1.0 & <2.0")); // >=1.0.0 & <2.0.0
|
||||||
|
assertFalse(v.satisfies("1 - 2")); // >=1.0.0 & <2.0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckIfStableVersionSatisfiesExpression() {
|
||||||
|
Version v = Version.valueOf("2.1.0");
|
||||||
|
assertTrue(v.satisfies("*")); // >=0.0.0
|
||||||
|
assertTrue(v.satisfies("x")); // >=0.0.0
|
||||||
|
assertTrue(v.satisfies("X")); // >=0.0.0
|
||||||
|
assertTrue(v.satisfies("2.*")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.x")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.X")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.1.*")); // >=2.1.0 <2.2.0
|
||||||
|
assertTrue(v.satisfies("2.1.x")); // >=2.1.0 <2.2.0
|
||||||
|
assertTrue(v.satisfies("2.1.X")); // >=2.1.0 <2.2.0
|
||||||
|
assertTrue(v.satisfies("2")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.0.0")); // >=2.0.0
|
||||||
|
assertTrue(v.satisfies("~2")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies(">=2.0 & <3.0")); // >=2.0.0 & <3.0.0
|
||||||
|
assertTrue(v.satisfies("2.0.0 - 2.1.0")); // >=2.0.0 & <=2.1.0
|
||||||
|
assertTrue(v.satisfies("2.0 - 2.1")); // >=2.0.0 & <=2.1.0
|
||||||
|
assertTrue(v.satisfies("^2")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("^2.0")); // >=2.0.0 <3.0.0
|
||||||
|
assertTrue(v.satisfies("^2.0.3")); // >=2.0.3 <3.0.0
|
||||||
|
assertFalse(v.satisfies("2.0")); // >=2.0.0 <2.1.0
|
||||||
|
assertFalse(v.satisfies("2.0.*")); // >=2.0.0 <2.1.0
|
||||||
|
assertFalse(v.satisfies("~2.0.0")); // >=2.0.0 <2.1.0
|
||||||
|
assertFalse(v.satisfies("~2.0")); // >=2.0.0 <2.1.0
|
||||||
|
assertFalse(v.satisfies("~1")); // >=1.0.0 <2.0.0
|
||||||
|
assertFalse(v.satisfies("~1.2.3")); // >=1.2.3 <1.3.0
|
||||||
|
assertFalse(v.satisfies("~1.2")); // >=1.2.0 <1.3.0
|
||||||
|
assertFalse(v.satisfies("~2.2.3")); // >=2.2.3 <2.3.0
|
||||||
|
assertFalse(v.satisfies("^0.2.3")); // >=0.2.3 <0.3.0
|
||||||
|
assertFalse(v.satisfies("^0.0.3")); // >=0.0.3 <0.0.4
|
||||||
|
assertFalse(v.satisfies("^0")); // >=0.0.0 <0.1.0
|
||||||
|
assertFalse(v.satisfies("^0.0")); // >=0.0.0 <0.1.0
|
||||||
|
assertFalse(v.satisfies("^0.0.0")); // >=0.0.0 <0.0.0
|
||||||
|
assertFalse(v.satisfies(">=1.0 & <2.0")); // >=1.0.0 & <2.0.0
|
||||||
|
assertFalse(v.satisfies("1 - 2")); // >=1.0.0 & <2.0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user