Skip to content
Merged
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
20 changes: 15 additions & 5 deletions composer_pub.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,18 @@ func parseComposerTildeRange(version string) (*Range, error) {
// parseComposerWildcardRange expands a Composer wildcard into inclusive and
// exclusive bounds.
func parseComposerWildcardRange(constraint string) (*Range, error) {
wildcard := strings.TrimSpace(constraint)
numericTagsOnly := wildcard != "*" && !strings.EqualFold(wildcard, "x")
parts, ok := composerWildcardReleaseParts(constraint)
if !ok {
return nil, fmt.Errorf("invalid composer wildcard: %s", constraint)
}
if len(parts) == 0 {
if numericTagsOnly {
return rangeWithScheme(NewRange([]Interval{
NewInterval("0.0.0.0-dev", "", true, false),
}), schemeComposer), nil
}
return rangeWithScheme(Unbounded(), schemeComposer), nil
}
lower := completeComposerRelease(parts) + "-dev"
Expand Down Expand Up @@ -474,8 +481,8 @@ func parseComposerVersion(version string) (composerVersion, bool) {
func compareComposer(a, b string) int {
left, leftOK := parseComposerVersion(a)
right, rightOK := parseComposerVersion(b)
leftBranch := strings.HasPrefix(strings.ToLower(a), "dev-")
rightBranch := strings.HasPrefix(strings.ToLower(b), "dev-")
leftBranch := isComposerBranchVersion(a)
rightBranch := isComposerBranchVersion(b)
if leftBranch != rightBranch {
if leftBranch {
return -1
Expand Down Expand Up @@ -524,9 +531,12 @@ func validComposerVersion(version string) bool {
if _, ok := parseComposerVersion(version); ok {
return true
}
lower := strings.ToLower(version)
return (strings.HasPrefix(lower, "dev-") && len(version) > len("dev-")) ||
composerNumericBranchRegex.MatchString(version)
return isComposerBranchVersion(version) || composerNumericBranchRegex.MatchString(version)
}

func isComposerBranchVersion(version string) bool {
version = strings.TrimSpace(version)
return len(version) > len("dev-") && strings.HasPrefix(strings.ToLower(version), "dev-")
}

// normalizeComposerVersion normalizes SemVer-shaped Composer tags while
Expand Down
37 changes: 37 additions & 0 deletions composer_pub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,40 @@ func TestComposerDevWildcardRanges(t *testing.T) {
}
}
}

func TestComposerRangeBranchContainment(t *testing.T) {
tests := []struct {
constraint string
version string
want bool
}{
{constraint: "*", version: "dev-master", want: true},
{constraint: "x", version: "dev-master", want: true},
{constraint: "v*", version: "dev-master", want: false},
{constraint: "v*", version: "1.0.0", want: true},
{constraint: "*.*", version: "dev-master", want: false},
{constraint: "<1.2.3", version: "dev-master", want: false},
{constraint: "=dev-master", version: "dev-master", want: true},
{constraint: "=dev-master", version: "1.0.0", want: false},
{constraint: ">=dev-master", version: "dev-master", want: false},
{constraint: ">=dev-master", version: "1.0.0", want: false},
{constraint: "!=dev-master", version: "dev-master", want: false},
{constraint: "!=dev-master", version: "dev-MASTER", want: true},
{constraint: "!=dev-master", version: "dev-main", want: true},
{constraint: "!=dev-master", version: "1.0.0", want: true},
}
for _, test := range tests {
native, err := ParseNative(test.constraint, schemeComposer)
if err != nil {
t.Fatalf("ParseNative(%q, composer): %v", test.constraint, err)
}
uri := ToVersString(native, schemeComposer)
roundTrip, err := Parse(uri)
if err != nil {
t.Fatalf("Parse(%q): %v", uri, err)
}
if got := roundTrip.Contains(test.version); got != test.want {
t.Errorf("Parse(%q).Contains(%q) = %v, want %v", uri, test.version, got, test.want)
}
}
}
30 changes: 28 additions & 2 deletions range.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,25 @@ func (r *Range) Contains(version string) bool {

// Check exclusions first
for _, exc := range r.Exclusions {
if scheme == schemePyPI && pep440SpecifierEqual(version, exc) || scheme != schemePyPI && cmp(version, exc) == 0 {
excluded := cmp(version, exc) == 0
if scheme == schemePyPI {
excluded = pep440SpecifierEqual(version, exc)
} else if scheme == schemeComposer && (isComposerBranchVersion(version) || isComposerBranchVersion(exc)) {
excluded = version == exc
}
if excluded {
return false
}
}

// Check if version is in any interval
for _, interval := range r.Intervals {
contains := interval.containsCmp(version, cmp)
if scheme == schemePyPI {
switch scheme {
case schemePyPI:
contains = pypiIntervalContains(interval, version)
case schemeComposer:
contains = composerIntervalContains(interval, version)
}
if contains && (scheme == schemeNPM || scheme == schemeCargo) && !semverIntervalAllowsPrerelease(interval, version) {
contains = false
Expand All @@ -60,6 +69,23 @@ func (r *Range) Contains(version string) bool {
return false
}

func composerIntervalContains(interval Interval, version string) bool {
candidateIsBranch := isComposerBranchVersion(version)
minimumIsBranch := isComposerBranchVersion(interval.Min)
maximumIsBranch := isComposerBranchVersion(interval.Max)
if !candidateIsBranch && !minimumIsBranch && !maximumIsBranch {
return interval.containsCmp(version, compareComposer)
}
if interval.IsUnbounded() {
return true
}
if candidateIsBranch && minimumIsBranch && maximumIsBranch &&
interval.MinInclusive && interval.MaxInclusive && interval.Min == interval.Max {
return version == interval.Min
}
return false
}

func semverIntervalAllowsPrerelease(interval Interval, version string) bool {
candidate, err := ParseVersion(version)
if err != nil || candidate.Prerelease == "" {
Expand Down
21 changes: 14 additions & 7 deletions testdata/local/provenance.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
],
"generated_files": [
"tests/npm_version_cmp_test.json",
"tests/npm_range_reference_test.json"
"tests/npm_range_reference_test.json",
"tests/npm_range_generated_test.json"
]
},
{
Expand All @@ -39,7 +40,8 @@
],
"generated_files": [
"tests/pypi_version_cmp_test.json",
"tests/pypi_range_reference_test.json"
"tests/pypi_range_reference_test.json",
"tests/pypi_range_generated_test.json"
]
},
{
Expand All @@ -52,7 +54,8 @@
],
"generated_files": [
"tests/gem_version_cmp_test.json",
"tests/gem_range_reference_test.json"
"tests/gem_range_reference_test.json",
"tests/gem_range_generated_test.json"
]
},
{
Expand All @@ -65,7 +68,8 @@
],
"generated_files": [
"tests/composer_version_cmp_test.json",
"tests/composer_range_reference_test.json"
"tests/composer_range_reference_test.json",
"tests/composer_range_generated_test.json"
]
},
{
Expand All @@ -80,7 +84,8 @@
],
"generated_files": [
"tests/pub_version_cmp_test.json",
"tests/pub_range_reference_test.json"
"tests/pub_range_reference_test.json",
"tests/pub_range_generated_test.json"
]
},
{
Expand All @@ -93,7 +98,8 @@
],
"generated_files": [
"tests/cargo_version_cmp_test.json",
"tests/cargo_range_reference_test.json"
"tests/cargo_range_reference_test.json",
"tests/cargo_range_generated_test.json"
]
},
{
Expand All @@ -104,7 +110,8 @@
"compat/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/VersionRangeTest.java"
],
"generated_files": [
"tests/maven_range_reference_test.json"
"tests/maven_range_reference_test.json",
"tests/maven_range_generated_test.json"
]
},
{
Expand Down
Loading