diff --git a/README.md b/README.md index c1c3499..8505caf 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,8 @@ differences to notes between these two methods of comparison. different set of rules that are common for ranges with tools like npm/js and Rust/Cargo. This includes considering prereleases to be invalid if the ranges does not include one. If you want to have it include pre-releases a - simple solution is to include `-0` in your range. + simple solution is to include `-0` in your range. For example, `~1.2.3-0` or + `1.2.3-0 - 1.5.0`. 3. Constraint ranges can have some complex rules including the shorthand use of ~ and ^. For more details on those see the options below. diff --git a/constraints.go b/constraints.go index 8461c7e..6fd3089 100644 --- a/constraints.go +++ b/constraints.go @@ -19,7 +19,7 @@ type Constraints struct { func NewConstraint(c string) (*Constraints, error) { // Rewrite - ranges into a comparison operation. - c = rewriteRange(c) + c, rangesWithPrerelease := rewriteRange(c) ors := strings.Split(c, "||") or := make([][]*constraint, len(ors)) @@ -43,6 +43,13 @@ func NewConstraint(c string) (*Constraints, error) { return nil, err } + // When we parse a constraint we lose information about if it was part of a range + // Use rangesWithPrerelease to check if the range originally used a pre-release + pcStr := pc.string() + if _, ok := rangesWithPrerelease[pcStr]; ok { + pc.allowPrerelease = true + } + result[i] = pc } or[k] = result @@ -224,6 +231,10 @@ type constraint struct { minorDirty bool dirty bool patchDirty bool + + // allowPrerelease indicates if the constraint should allow pre-releases, + // such as when part of a range constraint that uses pre-releases + allowPrerelease bool } // Check if a version meets the constraint @@ -279,6 +290,7 @@ func parseConstraint(c string) (*constraint, error) { cs.minorDirty = minorDirty cs.patchDirty = patchDirty cs.dirty = dirty + cs.allowPrerelease = cs.con.Prerelease() != "" return cs, nil } @@ -411,7 +423,7 @@ func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { + if v.Prerelease() != "" && !c.allowPrerelease { return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } @@ -426,7 +438,7 @@ func constraintLessThanEqual(v *Version, c *constraint) (bool, error) { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { + if v.Prerelease() != "" && !c.allowPrerelease { return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } @@ -459,7 +471,7 @@ func constraintTilde(v *Version, c *constraint) (bool, error) { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { + if v.Prerelease() != "" && !c.allowPrerelease { return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } @@ -491,7 +503,7 @@ func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { + if v.Prerelease() != "" && !c.allowPrerelease { return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } @@ -520,7 +532,7 @@ func constraintCaret(v *Version, c *constraint) (bool, error) { // If there is a pre-release on the version but the constraint isn't looking // for them assume that pre-releases are not compatible. See issue 21 for // more details. - if v.Prerelease() != "" && c.con.Prerelease() == "" { + if v.Prerelease() != "" && !c.allowPrerelease { return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) } @@ -579,16 +591,27 @@ func isX(x string) bool { } } -func rewriteRange(i string) string { +func rewriteRange(i string) (string, map[string]struct{}) { m := constraintRangeRegex.FindAllStringSubmatch(i, -1) if m == nil { - return i + return i, nil } o := i + allowPrerelease := make(map[string]struct{}, 0) for _, v := range m { t := fmt.Sprintf(">= %s, <= %s ", v[1], v[11]) o = strings.Replace(o, v[0], t, 1) + + // Check if any part of the range uses a pre-release + rangeUsesPrerelease := v[5] != "" || v[15] != "" + if rangeUsesPrerelease { + // do not use spaces, so that we can compare this later against a parsed constraint + lower := fmt.Sprintf(">=%s", v[1]) + upper := fmt.Sprintf("<=%s", v[11]) + allowPrerelease[lower] = struct{}{} + allowPrerelease[upper] = struct{}{} + } } - return o + return o, allowPrerelease } diff --git a/constraints_test.go b/constraints_test.go index c725a95..b761b63 100644 --- a/constraints_test.go +++ b/constraints_test.go @@ -400,6 +400,15 @@ func TestConstraintsCheck(t *testing.T) { // Ranges should work in conjunction with other constraints anded together. {"1.0.0 - 2.0.0 <=2.0.0", "1.5.0", true}, {"1.0.0 - 2.0.0, <=2.0.0", "1.5.0", true}, + + // Should fail because 1.5.0 is AND'd with the range and since it does not allow prereleases, the entire constraint fails + {"1.0.0-alpha.1 - 1.0.0, <= 1.5.0", "1.0.0-beta.2", false}, + // Should fail because only the first range should allow prereleases (detects if we are allowing prereleases for more than the affected range + {"1.0.0-alpha.1 - 1.0.0 || <= 1.5.0", "1.5.0-beta.2", false}, + // When the lower part of the range allows prereleases, the upper should also allow prereleases + {"1.0.0-alpha.1 - 1.0.0", "1.0.0-beta.2", true}, + // When the upper part of the range allows prereleases, the lower should also allow prereleases + {"1.0.0 - 2.0.0-beta.3", "2.0.0-beta.2", true}, } for _, tc := range tests { @@ -435,7 +444,7 @@ func TestRewriteRange(t *testing.T) { } for _, tc := range tests { - o := rewriteRange(tc.c) + o, _ := rewriteRange(tc.c) if o != tc.nc { t.Errorf("Range %s rewritten incorrectly as %q instead of expected %q", tc.c, o, tc.nc)