Skip to content

Commit

Permalink
Support for version constraint wildcards
Browse files Browse the repository at this point in the history
  • Loading branch information
beornf committed Feb 12, 2023
1 parent 637058c commit eac47a9
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 52 deletions.
90 changes: 77 additions & 13 deletions pkg/version/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"regexp"
"strings"

"github.com/aquasecurity/go-version/pkg/part"
"github.com/aquasecurity/go-version/pkg/prerelease"
"golang.org/x/xerrors"
)

Expand All @@ -30,6 +32,11 @@ var (

type operatorFunc func(v, c Version) bool

const cvRegex = `v?([0-9|x|X|\*]+(\.[0-9|x|X|\*]+)*)` +
`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
`?`

func init() {
ops := make([]string, 0, len(constraintOperators))
for k := range constraintOperators {
Expand All @@ -39,12 +46,12 @@ func init() {
constraintRegexp = regexp.MustCompile(fmt.Sprintf(
`(%s)\s*(%s)`,
strings.Join(ops, "|"),
regex))
cvRegex))

validConstraintRegexp = regexp.MustCompile(fmt.Sprintf(
`^\s*(\s*(%s)\s*(%s)\s*\,?)*\s*$`,
strings.Join(ops, "|"),
regex))
cvRegex))
}

// Constraints is one or more constraint that a version can be checked against.
Expand Down Expand Up @@ -90,23 +97,64 @@ func NewConstraints(v string) (Constraints, error) {
}

func newConstraint(c string) (constraint, error) {
o := prereleaseCheck
if c == "" {
return constraint{
version: Version{},
operator: o,
original: c,
}, nil
}

m := constraintRegexp.FindStringSubmatch(c)
if m == nil {
return constraint{}, xerrors.Errorf("improper constraint: %s", c)
}

v, err := Parse(m[2])
v, err := newConstraintVersion(m[2:])
if err != nil {
return constraint{}, xerrors.Errorf("version parse error (%s): %w", m[2], err)
}

if len(v.segments) > 0 {
o = constraintOperators[m[1]]
}

return constraint{
version: v,
operator: constraintOperators[m[1]],
operator: o,
original: c,
}, nil
}

func newConstraintVersion(matches []string) (Version, error) {
var segments []part.Uint64
for _, str := range strings.Split(matches[1], ".") {
if _, err := part.NewAny(str); err == nil {
break
}

val, err := part.NewUint64(str)
if err != nil {
return Version{}, xerrors.Errorf("error parsing version: %w", err)
}

segments = append(segments, val)
}

pre := matches[7]
if pre == "" {
pre = matches[4]
}

return Version{
segments: segments,
buildMetadata: matches[10],
preRelease: part.NewParts(pre),
original: matches[0],
}, nil
}

func (c constraint) check(v Version) bool {
return c.operator(v, c.version)
}
Expand Down Expand Up @@ -149,36 +197,52 @@ func andCheck(v Version, constraints []constraint) bool {
return true
}

func prereleaseCheck(v, c Version) bool {
if !v.preRelease.IsNull() && c.preRelease.IsNull() {
return false
}
return true
}

//-------------------------------------------------------------------
// Constraint functions
//-------------------------------------------------------------------

func constraintEqual(v, c Version) bool {
return v.Equal(c)
if prerelease.Compare(v.preRelease, c.preRelease) != 0 {
return false
}
return v.GreaterThanOrEqual(c) && v.LessThan(c.NextBump())
}

func constraintNotEqual(v, c Version) bool {
return !v.Equal(c)
return !constraintEqual(v, c)
}

func constraintGreaterThan(v, c Version) bool {
return v.GreaterThan(c)
if !prereleaseCheck(v, c) {
return false
}
if !v.preRelease.IsNull() {
return v.GreaterThan(c)
}
return v.GreaterThanOrEqual(c.NextBump())
}

func constraintLessThan(v, c Version) bool {
return v.LessThan(c)
return prereleaseCheck(v, c) && v.LessThan(c)
}

func constraintGreaterThanEqual(v, c Version) bool {
return v.GreaterThanOrEqual(c)
return prereleaseCheck(v, c) && v.GreaterThanOrEqual(c)
}

func constraintLessThanEqual(v, c Version) bool {
return v.LessThanOrEqual(c)
return prereleaseCheck(v, c) && v.LessThan(c.NextBump())
}

func constraintPessimistic(v, c Version) bool {
return v.GreaterThanOrEqual(c) && v.LessThan(c.PessimisticBump())
return prereleaseCheck(v, c) && v.GreaterThanOrEqual(c) && v.LessThan(c.PessimisticBump())
}

func constraintTilde(v, c Version) bool {
Expand All @@ -188,7 +252,7 @@ func constraintTilde(v, c Version) bool {
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
return v.GreaterThanOrEqual(c) && v.LessThan(c.TildeBump())
return prereleaseCheck(v, c) && v.GreaterThanOrEqual(c) && v.LessThan(c.TildeBump())
}

func constraintCaret(v, c Version) bool {
Expand All @@ -201,5 +265,5 @@ func constraintCaret(v, c Version) bool {
// ^0.0.3 --> >=0.0.3 <0.0.4
// ^0.0 --> >=0.0.0 <0.1.0
// ^0 --> >=0.0.0 <1.0.0
return v.GreaterThanOrEqual(c) && v.LessThan(c.CaretBump())
return prereleaseCheck(v, c) && v.GreaterThanOrEqual(c) && v.LessThan(c.CaretBump())
}
72 changes: 55 additions & 17 deletions pkg/version/constraint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestVersion_Check(t *testing.T) {
{"=4.1-alpha", "4.1.0-alpha", true},
{"=2.0", "1.2.3", false},
{"=2.0", "2.0.0", true},
{"=2.0", "2.0.1", false},
{"=2.0", "2.0.1", true},
{"=0", "1.0.0", false},

{"== 2.0.0", "1.2.3", false},
Expand All @@ -62,49 +62,61 @@ func TestVersion_Check(t *testing.T) {

{"2", "1.0.0", false},
{"2", "3.4.5", false},
{"2", "2.1.1", false},
{"2.1", "2.1.1", false},
{"2", "2.1.1", true},
{"2.1", "2.1.1", true},
{"2.1", "2.2.1", false},
{"4.1", "4.1.0", true},
{"1.0", "1.0.0", true},
{"1.x", "1.2.3", true},
{"4.1.x", "4.1.3", true},

// Not equal
{"!=4.1.0", "4.1.0", false},
{"!=4.1.0", "4.1.1", true},
{"!=4.1", "5.1.0-alpha.1", true},
{"!=4.1-alpha", "4.1.0", true},
{"!=4.x", "5.1.0", true},
{"!=4.1.x", "4.2.0", true},
{"!=4.2.x", "4.2.3", false},

// Less than
{"<0.0.5", "0.1.0", false},
{"<1.0.0", "0.1.0", true},
{"<0", "0.0.0-alpha", true},
{"<0", "0.0.0-alpha", false},
{"<0-z", "0.0.0-alpha", true},
{"<0", "1.0.0-alpha", false},
{"<1", "1.0.0-alpha", true},
{"<1", "1.0.0-alpha", false},
{"<11", "0.1.0", true},
{"<11", "11.1.0", false},
{"<1.1", "0.1.0", true},
{"<1.1", "1.1.0", false},
{"<1.1", "1.1.1", false},
{"<1.x", "1.1.1", false},
{"<2.x", "1.1.1", true},
{"<1.1.x", "1.2.1", false},
{"<1.2.x", "1.1.1", true},

// Less than or equal
{"<=0.2.3", "1.2.3", false},
{"<=1.2.3", "1.2.3", true},
{"<= 2.1.0-a", "2.0.0", true},
{"<=11", "1.2.3", true},
{"<=11", "12.2.3", false},
{"<=11", "11.2.3", false}, // different
{"<=11", "11.2.3", true},
{"<=1.1", "1.2.3", false},
{"<=1.1", "0.1.0", true},
{"<=1.1", "1.1.0", true},
{"<=1.1", "1.1.1", false}, // different
{"<=1.1", "1.1.1", true},
{"<=1.x", "1.1.1", true},
{"<=2.x", "3.0.0", false},
{"<=1.1.x", "1.2.1", false},

// Greater than
{">5.0.0", "4.1.0", false},
{">4.0.0", "4.1.0", true},
{"> 2.0", "2.1.0-beta", true},
{">0", "0.0.1-alpha", true},
{">0.0", "0.0.1-alpha", true},
{"> 2.0", "2.1.0-beta", false},
{">0", "0.0.1-alpha", false},
{">0.0", "0.0.1-alpha", false},
{">0-0", "0.0.1-alpha", true},
{">0.0-0", "0.0.1-alpha", true},
{">0", "0.0.0-alpha", false},
Expand All @@ -116,22 +128,24 @@ func TestVersion_Check(t *testing.T) {
{">1.1", "1.1.0", false},
{">0", "0.0.0", false},
{">0", "1.0.0", true},
{">11", "11.1.0", true}, // different
{">11", "11.1.0", false},
{">11.1", "11.1.0", false},
{">11.1", "11.1.1", true}, // different
{">11.1", "11.1.1", false},
{">11.1", "11.2.1", true},
{">11.x", "11.2.1", false},
{">11.1.x", "11.2.1", true},

// Greater than or equal
{">=11.1.3", "11.1.2", false},
{">=11.1.2", "11.1.2", true},
{">= 1.0, < 1.2", "1.1.5", true},
{">= 2.1.0-a", "2.1.0-beta", true},
{">= 2.1.0-a", "2.1.1-beta", true},
{">= 2.0.0", "2.1.0-beta", true},
{">= 2.0.0", "2.1.0-beta", false},
{">= 2.1.0-a", "2.1.1", true},
{">= 2.1.0-a", "2.1.0", true},
{">=0", "0.0.1-alpha", true},
{">=0.0", "0.0.1-alpha", true},
{">=0", "0.0.1-alpha", false},
{">=0.0", "0.0.1-alpha", false},
{">=0-0", "0.0.1-alpha", true},
{">=0.0-0", "0.0.1-alpha", true},
{">=0", "0.0.0-alpha", false},
Expand All @@ -146,6 +160,8 @@ func TestVersion_Check(t *testing.T) {
{">=1.1", "1.1.0", true},
{">=1.1", "0.0.9", false},
{">=0", "0.0.0", true},
{">=11.x", "11.1.2", true},
{">=11.1.x", "11.1.2", true},

// Pessimistic
{"~> 1.0", "2.0", false},
Expand All @@ -165,11 +181,15 @@ func TestVersion_Check(t *testing.T) {
{"~> 1.0.9.5", "1.0.9.6", true},
{"~> 1.0.9.5", "1.0.9.5.0", true},
{"~> 1.0.9.5", "1.0.9.5.1", true},
{"~> 2.0", "2.1.0-beta", true},
{"~> 2.0", "2.1.0-beta", false},
{"~> 2.1.0-a", "2.2.0", false},
{"~> 2.1.0-a", "2.1.0", true},
{"~> 2.1.0-a", "2.1.0-beta", true},
{"~> 2.1.0-a", "2.2.0-alpha", true},
{"~> 1.x", "2.0", false},
{"~> 1.x", "1.1", true},
{"~> 1.0.x", "1.2.3", true},
{"~> 1.0.x", "1.0.7", true},

// Tilde
{"~1.2.3", "1.2.4", true},
Expand All @@ -184,6 +204,8 @@ func TestVersion_Check(t *testing.T) {
{"~1.2.3-beta.2", "1.2.3-beta.4", true},
{"~1.2.3-beta.2", "1.2.4-beta.2", true},
{"~1.2.3-beta.2", "1.3.4-beta.2", false},
{"~1.x", "2.1.1", false},
{"~1.x", "1.3.5", true},

// Caret
{"^1.2.3", "1.8.9", true},
Expand All @@ -206,14 +228,30 @@ func TestVersion_Check(t *testing.T) {
{"^0.0", "1.0.4", false},
{"^0", "0.2.3", true},
{"^0", "1.1.4", false},
{"^1.2.0", "1.2.1-alpha.1", true},
{"^1.2.0", "1.2.1-alpha.1", false},
{"^1.2.0-alpha.0", "1.2.1-alpha.1", true},
{"^1.2.0-alpha.0", "1.2.1-alpha.0", true},
{"^1.2.0-alpha.2", "1.2.0-alpha.1", false},
{"^0.2.3-beta.2", "0.2.3-beta.4", true},
{"^0.2.3-beta.2", "0.2.4-beta.2", true},
{"^0.2.3-beta.2", "0.3.4-beta.2", false},
{"^0.2.3-beta.2", "0.2.3-beta.2", true},
{"^1.x", "1.1.1", true},
{"^2.x", "1.1.1", false},

// Wildcards
{"", "1", true},
{"", "4.5.6", true},
{"", "1.2.3-alpha.1", false},
{"*", "1", true},
{"*", "4.5.6", true},
{"*", "1.2.3-alpha.1", false},
{"*-0", "1.2.3-alpha.1", true},
{"2.*", "1", false},
{"2.*", "3.4.5", false},
{"2.*", "2.1.1", true},
{"2.1.*", "2.1.1", true},
{"2.1.*", "2.2.1", false},

// More than 3 numbers
{"< 1.0.0.1 || = 2.0.1.2.3", "2.0", false},
Expand Down
Loading

0 comments on commit eac47a9

Please sign in to comment.