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 java.util.EnumSet;
|
||||
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.Lexer.Token.Type.*;
|
||||
|
||||
/**
|
||||
* A parser for the SemVer Expressions.
|
||||
@ -152,6 +152,7 @@ public class ExpressionParser implements Parser<Expression> {
|
||||
* <expr> ::= <comparison-expr>
|
||||
* | <version-expr>
|
||||
* | <tilde-expr>
|
||||
* | <caret-expr>
|
||||
* | <range-expr>
|
||||
* }
|
||||
* </pre>
|
||||
@ -161,6 +162,8 @@ public class ExpressionParser implements Parser<Expression> {
|
||||
private CompositeExpression parseExpression() {
|
||||
if (tokens.positiveLookahead(TILDE)) {
|
||||
return parseTildeExpression();
|
||||
} if (tokens.positiveLookahead(CARET)) {
|
||||
return parseCaretExpression();
|
||||
} else if (isVersionExpression()) {
|
||||
return parseVersionExpression();
|
||||
} else if (isRangeExpression()) {
|
||||
@ -230,18 +233,61 @@ public class ExpressionParser implements Parser<Expression> {
|
||||
consumeNextToken(TILDE);
|
||||
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||
if (!tokens.positiveLookahead(DOT)) {
|
||||
return gte(versionFor(major));
|
||||
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
||||
}
|
||||
consumeNextToken(DOT);
|
||||
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||
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);
|
||||
int patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||
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
|
||||
* of the {@literal <version-expr>} non-terminal.
|
||||
@ -251,7 +297,7 @@ public class ExpressionParser implements Parser<Expression> {
|
||||
* {@code false} otherwise
|
||||
*/
|
||||
private boolean isVersionExpression() {
|
||||
return isVersionFollowedBy(STAR);
|
||||
return isVersionFollowedBy(WILDCARD) || isVersionFollowedBy(EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,26 +305,50 @@ public class ExpressionParser implements Parser<Expression> {
|
||||
*
|
||||
* <pre>
|
||||
* {@literal
|
||||
* <version-expr> ::= <major> "." "*"
|
||||
* | <major> "." <minor> "." "*"
|
||||
* <version-expr> ::= <wildcard>
|
||||
* | <major> "." <wildcard>
|
||||
* | <major> "." <minor> "." <wildcard>
|
||||
*
|
||||
* <wildcard> ::= "*" | "x" | "X"
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return the expression AST
|
||||
*/
|
||||
private CompositeExpression parseVersionExpression() {
|
||||
if (tokens.positiveLookahead(WILDCARD)) {
|
||||
tokens.consume();
|
||||
return gte(versionFor(0, 0, 0));
|
||||
}
|
||||
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||
if (tokens.positiveLookahead(DOT)) {
|
||||
consumeNextToken(DOT);
|
||||
if (tokens.positiveLookahead(STAR)) {
|
||||
}
|
||||
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();
|
||||
return gte(versionFor(major)).and(lt(versionFor(major + 1)));
|
||||
}
|
||||
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||
if (tokens.positiveLookahead(DOT)) {
|
||||
consumeNextToken(DOT);
|
||||
consumeNextToken(STAR);
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the following version terminals are
|
||||
* part of the {@literal <range-expr>} non-terminal.
|
||||
|
@ -57,7 +57,8 @@ class Lexer {
|
||||
LESS("<(?!=)"),
|
||||
LESS_EQUAL("<="),
|
||||
TILDE("~"),
|
||||
STAR("\\*"),
|
||||
WILDCARD("[\\*xX]"),
|
||||
CARET("\\^"),
|
||||
AND("&"),
|
||||
OR("\\|"),
|
||||
NOT("!(?!=)"),
|
||||
|
@ -26,7 +26,8 @@ package com.github.zafarkhaja.semver;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
@ -307,13 +308,6 @@ public class VersionTest {
|
||||
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
|
||||
public void shouldCheckIfVersionSatisfiesExpression() {
|
||||
Version v = Version.valueOf("2.0.0-beta");
|
||||
|
@ -89,7 +89,7 @@ public class ExpressionParserTest {
|
||||
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||
Expression expr1 = parser.parse("~1");
|
||||
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");
|
||||
assertTrue(expr2.interpret(Version.valueOf("1.2.3")));
|
||||
assertFalse(expr2.interpret(Version.valueOf("2.0.0")));
|
||||
@ -201,4 +201,81 @@ public class ExpressionParserTest {
|
||||
}
|
||||
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