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

Allow pre-releases when the range uses them #184

Open
wants to merge 5 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -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.

Expand Down
41 changes: 32 additions & 9 deletions constraints.go
Expand Up @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}
11 changes: 10 additions & 1 deletion constraints_test.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down