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

strict version checking feature #243

Open
wants to merge 3 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,25 @@ greater than or equal to 4.2.3.
The basic comparisons are:

* `=`: equal (aliased to no operator)
* `==`: strictly equal
* `!=`: not equal
* `>`: greater than
* `<`: less than
* `>=`: greater than or equal to
* `<=`: less than or equal to

### Strict Comparisons

The double equal (`==`) operator is used to check if a version is really strictly equal to
the passed version.
These look like:

* `==1.2.1` will only look for a `1.2.1` version, metadatas or preleased versions will be considered different from this version.
* `==1.2.1-alpha` will only look for a `1.2.1-alpha` preleased version
* `==1.2.1+alpha` will only look for a `1.2.1-alpha` preleased version

> This helps looking for a very specific version only, the `=` operator (or the no operator) will be ok with metadatas in the version in the first place, ex: `=1.2.1` will be equivalent to `=1.2.1+x` where `x` can be any medatata.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As described here, the issue lied in here, we are not able to differentiate a version with metadatas and a version without metadatas with the actual constraints comparisons possibilities


### Working With Prerelease Versions

Pre-releases, for those not familiar with them, are used for software releases
Expand Down Expand Up @@ -177,7 +190,7 @@ parsed as a single constraint `1.2.0` with _prerelease_ `1.4.5`.
### Wildcards In Comparisons

The `x`, `X`, and `*` characters can be used as a wildcard character. This works
for all comparison operators. When used on the `=` operator it falls
for all comparison operators except the strict operators. When used on the `=` operator it falls
back to the patch level comparison (see tilde below). For example,

* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
Expand Down
28 changes: 17 additions & 11 deletions constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ type Constraints struct {
// NewConstraint returns a Constraints instance that a Version instance can
// be checked against. If there is a parse error it will be returned.
func NewConstraint(c string) (*Constraints, error) {

// Rewrite - ranges into a comparison operation.
c = rewriteRange(c)

Expand Down Expand Up @@ -97,7 +96,6 @@ func (cs Constraints) Validate(v *Version) (bool, []error) {
joy = false

} else {

if _, err := c.check(v); err != nil {
e = append(e, err)
joy = false
Expand Down Expand Up @@ -151,9 +149,11 @@ func (cs Constraints) MarshalText() ([]byte, error) {
return []byte(cs.String()), nil
}

var constraintOps map[string]cfunc
var constraintRegex *regexp.Regexp
var constraintRangeRegex *regexp.Regexp
var (
constraintOps map[string]cfunc
constraintRegex *regexp.Regexp
constraintRangeRegex *regexp.Regexp
)

// Used to find individual constraints within a multi-constraint string
var findConstraintRegex *regexp.Regexp
Expand All @@ -174,14 +174,15 @@ func init() {
"<": constraintLessThan,
">=": constraintGreaterThanEqual,
"=>": constraintGreaterThanEqual,
"==": constraintEqual,
"<=": constraintLessThanEqual,
"=<": constraintLessThanEqual,
"~": constraintTilde,
"~>": constraintTilde,
"^": constraintCaret,
}

ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^`
ops := `=||!=|==|>|<|>=|=>|<=|=<|~|~>|\^`

constraintRegex = regexp.MustCompile(fmt.Sprintf(
`^\s*(%s)\s*(%s)\s*$`,
Expand Down Expand Up @@ -269,7 +270,6 @@ func parseConstraint(c string) (*constraint, error) {

con, err := NewVersion(ver)
if err != nil {

// The constraintRegex should catch any regex parsing errors. So,
// we should never get here.
return nil, errors.New("constraint Parser Error")
Expand All @@ -287,7 +287,6 @@ func parseConstraint(c string) (*constraint, error) {
// is equivalent to * or >=0.0.0
con, err := StrictNewVersion("0.0.0")
if err != nil {

// The constraintRegex should catch any regex parsing errors. So,
// we should never get here.
return nil, errors.New("constraint Parser Error")
Expand All @@ -307,7 +306,6 @@ func parseConstraint(c string) (*constraint, error) {
// Constraint functions
func constraintNotEqual(v *Version, c *constraint) (bool, error) {
if c.dirty {

// 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.
Expand Down Expand Up @@ -345,8 +343,17 @@ func constraintNotEqual(v *Version, c *constraint) (bool, error) {
return true, nil
}

func constraintGreaterThan(v *Version, c *constraint) (bool, error) {
// Constraint functions
func constraintEqual(v *Version, c *constraint) (bool, error) {
Diliz marked this conversation as resolved.
Show resolved Hide resolved
eq := v.Original() == c.orig
if eq {
return true, nil
}

return false, fmt.Errorf("%s is not equal to %s", v, c.orig)
}

func constraintGreaterThan(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.
Expand Down Expand Up @@ -407,7 +414,6 @@ func constraintLessThan(v *Version, c *constraint) (bool, error) {
}

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.
Expand Down
44 changes: 44 additions & 0 deletions constraints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ func TestParseConstraint(t *testing.T) {
}{
{">= 1.2", constraintGreaterThanEqual, "1.2.0", false},
{"1.0", constraintTildeOrEqual, "1.0.0", false},
{"==1.0", constraintEqual, "1.0.0", false},
{"==1", constraintEqual, "1.0.0", false},
{"foo", nil, "", true},
{"<= 1.2", constraintLessThanEqual, "1.2.0", false},
{"=< 1.2", constraintLessThanEqual, "1.2.0", false},
Expand Down Expand Up @@ -138,6 +140,23 @@ func TestConstraintCheck(t *testing.T) {
{"2", "2.1.1", true},
{"2.1", "2.1.1", true},
{"2.1", "2.2.1", false},
{"==2", "1", false},
{"==2", "3.4.5", false},
{"==2", "2.0.0", false},
{"==2", "2.0.0+alpha", false},
{"==2", "2.0.0-alpha", false},
{"==2", "2.0.1", false},
{"==2.1", "2.1.0", false},
{"==2.1.x", "2.1.0", false},
{"==2.1.x", "2.1.1", false},
Comment on lines +143 to +151
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't have a version that's "2". So, a constraint of ==2 can never be matched against.

Since other comparisons allow shortened versions with assumptions on missing parts this looks to be confusing to user input that's run through here. That, I think, means more support requests.

node-semver does == and === which you can find out about at https://github.com/npm/node-semver?tab=readme-ov-file#comparison.

When I look at === with missing parts (e.g., ===1) it falls into a loose rather than exact string situation. but, if you put a full semver in there is a string comparison.

What do you think of the node-semver model?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your point, since there was no double equal operator I didn't thinked too much about it, maybe I am wrong, here what I think should be some constraints examples that follows the node-semver model:

  • simple equal operator = :
"=2.x.x", "2.0.1", true
"=2.x.x", "2.0.1+mybuild", true
"=2.x.x", "2.0.1-beta+mybuild", true
"=2.x.x+build", "2.0.1", false
"=2.x.x+build", "2.0.1+mybuild", true
"=2.x.x-beta+build", "2.0.1+mybuild", (true or false)? # In this case, in the node model, the version and the build would correspond, so it could be equal if beta is not taken into account
"=2.x.x-beta+build", "2.0.1-beta+mybuild", true
  • double equal operator == :
"==2.x.x", "2.0.1", true
"==2.x.x", "2.0.1+mybuild", false
"==2.x.x", "2.0.1-beta+mybuild", false
"==2.x.x+build", "2.0.1", false
"==2.x.x+build", "2.0.1+mybuild", true
"==2.x.x-beta+build", "2.0.1+mybuild", false
"==2.x.x-beta+build", "2.0.1-beta+mybuild", true
  • triple equal operator === :
"===2.x.x", "2.0.1", false
"===2.x.x", "2.0.1+mybuild", false
"===2.x.x", "2.0.1-beta+mybuild", false
"===2.x.x+build", "2.0.1", false
"===2.x.x+build", "2.0.1+mybuild", false
"===2.x.x-beta+build", "2.0.1+mybuild", false
"===2.x.x-beta+build", "2.0.1-beta+mybuild", false
"===2.0.1", "2.0.1", true
"===2.0.1", "2.0.1+mybuild", false
"===2.0.1", "2.0.1-beta+mybuild", false
"===2.0.1+build", "2.0.1", false
"===2.0.1+build", "2.0.1+mybuild", true
"===2.0.1-beta+build", "2.0.1+mybuild", false
"===2.0.1-beta+build", "2.0.1-beta+mybuild", true

I'm pretty much ok with the node-semver model, and this example as well, if we can be sure to find a specific version or subset of versions it would be perfect 👍

{"==2.1", "2.1.1", false},
{"==2.1", "2.2.1", false},
{"==2.1.1", "2.1.1", true},
{"==2.1.1", "2.2.1", false},
{"==2.1.1+alpha", "2.1.1+alpha.1", false},
{"==2.1.1+alpha.1", "2.1.1+alpha.1", true},
{"==2.1.1-alpha", "2.1.1-alpha.1", false},
{"==2.1.1-alpha.1", "2.1.1-alpha.1", true},
{"~1.2.3", "1.2.4", true},
{"~1.2.3", "1.3.4", false},
{"~1.2", "1.2.4", true},
Expand Down Expand Up @@ -290,7 +309,10 @@ func TestConstraintsCheck(t *testing.T) {
{"= 2.0", "1.2.3", false},
{"= 2.0", "2.0.0", true},
{"4.1", "4.1.0", true},
{"4.1", "4.1.3+alpha", true},
{"4.1.x", "4.1.3", true},
{"4.1.x", "4.1.3+alpha", true},
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the issue, it's also the case without dirty

{"4.1.3", "4.1.3+alpha", true},
{"1.x", "1.4", true},
{"!=4.1", "4.1.0", false},
{"!=4.1-alpha", "4.1.0-alpha", false},
Expand Down Expand Up @@ -474,14 +496,27 @@ func TestConstraintsValidate(t *testing.T) {
{"= 2.0", "1.2.3", false},
{"= 2.0", "2.0.0", true},
{"4.1", "4.1.0", true},
{"4.1", "4.1.3+alpha", true},
{"4.1.x", "4.1.3", true},
{"4.1.x", "4.1.3+alpha", true},
{"4.1.3", "4.1.3+alpha", true},
{"1.x", "1.4", true},
{"!=4.1", "4.1.0", false},
{"!=4.1", "5.1.0", true},
{"!=4.x", "5.1.0", true},
{"!=4.x", "4.1.0", false},
{"!=4.1.x", "4.2.0", true},
{"!=4.2.x", "4.2.3", false},
{"==4.1", "4.1.0", false},
{"==4.1", "5.1.0", false},
{"==4.x", "5.1.0", false},
{"==4.x", "4.1.0", false},
{"==4.1.x", "4.2.0", false},
{"==4.1.0", "4.1.0", true},
{"==4.1.0+alpha", "4.1.0+alpha", true},
{"==4.1.0+alpha", "4.1.0+alpha-1", false},
{"==4.1.0-alpha", "4.1.0-alpha-1", false},
{"==4.2.x", "4.2.3", false},
{">1.1", "4.1.0", true},
{">1.1", "1.1.0", false},
{"<1.1", "0.1.0", true},
Expand Down Expand Up @@ -600,6 +635,15 @@ func TestConstraintsValidate(t *testing.T) {
{"= 2.0", "1.2.3", "1.2.3 is less than 2.0"},
{"!=4.1", "4.1.0", "4.1.0 is equal to 4.1"},
{"!=4.x", "4.1.0", "4.1.0 is equal to 4.x"},
{"==4.x", "5.1.0", "5.1.0 is not equal to 4.x"},
{"==4.x", "4.1.0", "4.1.0 is not equal to 4.x"},
{"==4.1.x", "4.1.0", "4.1.0 is not equal to 4.1.x"},
{"==4.1.x", "4.2.0", "4.2.0 is not equal to 4.1.x"},
{"==4.1.2", "4.1.3", "4.1.3 is not equal to 4.1.2"},
{"==4.1.2-beta", "4.1.2-beta.1", "4.1.2-beta.1 is not equal to 4.1.2-beta"},
{"==4.1.2+beta", "4.1.2+beta.1", "4.1.2+beta.1 is not equal to 4.1.2+beta"},
{"==4.0.1", "4.0.1-beta.1", "4.0.1-beta.1 is a prerelease version and the constraint is only looking for release versions"},
{"==4.2", "4.1.0", "4.1.0 is not equal to 4.2"},
{"!=4.2.x", "4.2.3", "4.2.3 is equal to 4.2.x"},
{">1.1", "1.1.0", "1.1.0 is less than or equal to 1.1"},
{"<1.1", "1.1.0", "1.1.0 is greater than or equal to 1.1"},
Expand Down