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
68 changes: 65 additions & 3 deletions composer_pub.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (

const (
composerTildeBumpOffset = 2
composerVersionParts = 4
composerQualifierDev = "dev"
composerQualifierPatch = "patch"
composerQualifierStable = "stable"
Expand Down Expand Up @@ -170,6 +171,12 @@ func splitComposerStabilityFlag(constraint string) (string, string) {
// parseComposerCaretRange expands a Composer caret constraint and uses a dev
// upper bound so prereleases of the next breaking version are excluded.
func parseComposerCaretRange(version string) (*Range, error) {
if lower, parts, ok := composerDevWildcardBound(version); ok {
upper := incrementComposerRelease(parts[:1], 0)
return rangeWithScheme(NewRange([]Interval{
NewInterval(lower, upper, true, false),
}), schemeComposer), nil
}
parts, ok := composerReleaseParts(version)
if !ok {
return nil, fmt.Errorf("invalid composer caret version: %s", version)
Expand All @@ -192,6 +199,17 @@ func parseComposerCaretRange(version string) (*Range, error) {
// components allow the next major; longer versions allow the penultimate
// component to increase.
func parseComposerTildeRange(version string) (*Range, error) {
if lower, parts, ok := composerDevWildcardBound(version); ok {
prefixLength := 0
for prefixLength < len(parts) && parts[prefixLength] != "9999999" {
prefixLength++
}
upperParts := parts[:prefixLength]
upper := incrementComposerRelease(upperParts, len(upperParts)-1)
return rangeWithScheme(NewRange([]Interval{
NewInterval(lower, upper, true, false),
}), schemeComposer), nil
}
parts, ok := composerReleaseParts(version)
if !ok {
return nil, fmt.Errorf("invalid composer tilde version: %s", version)
Expand Down Expand Up @@ -256,19 +274,63 @@ func composerWildcardReleaseParts(constraint string) ([]string, bool) {
// version behaves as a wildcard, while a complete upper version is inclusive.
func parseComposerHyphenRange(lower, upper string) (*Range, error) {
_, lowerOK := composerReleaseParts(lower)
min, lowerWildcardParts, lowerWildcard := composerDevWildcardBound(lower)
if lowerWildcard {
lowerOK = len(lowerWildcardParts) > 0
} else {
min, _ = canonicalComposerNumericVersion(lower, composerQualifierDev)
}
upperParts, upperOK := composerReleaseParts(upper)
max, _, upperWildcard := composerDevWildcardBound(upper)
if upperWildcard {
upperOK = true
}
if !lowerOK || !upperOK {
return nil, fmt.Errorf("invalid composer hyphen range: %s - %s", lower, upper)
}
min, _ := canonicalComposerNumericVersion(lower, composerQualifierDev)
if upperWildcard {
return rangeWithScheme(NewRange([]Interval{NewInterval(min, max, true, true)}), schemeComposer), nil
}
if len(upperParts) < 3 && !hasComposerStability(upper) { //nolint:mnd
max := incrementComposerRelease(upperParts, len(upperParts)-1)
max = incrementComposerRelease(upperParts, len(upperParts)-1)
return rangeWithScheme(NewRange([]Interval{NewInterval(min, max, true, false)}), schemeComposer), nil
}
max, _ := canonicalComposerNumericVersion(upper, "")
max, _ = canonicalComposerNumericVersion(upper, "")
return rangeWithScheme(NewRange([]Interval{NewInterval(min, max, true, true)}), schemeComposer), nil
}

func composerDevWildcardBound(version string) (string, []string, bool) {
version = normalizeComposerLower(version)
if !strings.HasSuffix(strings.ToLower(version), "-dev") {
return "", nil, false
}
release := version[:len(version)-len("-dev")]
segments := strings.Split(release, ".")
parts := make([]string, 0, composerVersionParts)
wildcard := false
for _, segment := range segments {
if segment == "*" || strings.EqualFold(segment, "x") {
if len(parts) == 0 {
return "", nil, false
}
wildcard = true
parts = append(parts, "9999999")
continue
}
if wildcard || !isDigits(segment) {
return "", nil, false
}
parts = append(parts, trimLeadingZeros(segment))
}
if !wildcard || len(parts) > composerVersionParts {
return "", nil, false
}
for len(parts) < composerVersionParts {
parts = append(parts, "9999999")
}
return strings.Join(parts, ".") + "-dev", parts, true
}

// hasComposerStability reports whether a numeric Composer version has an
// explicit stability suffix.
func hasComposerStability(version string) bool {
Expand Down
26 changes: 25 additions & 1 deletion composer_pub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package vers
import "testing"

func TestParseComposerRangeErrors(t *testing.T) {
for _, constraint := range []string{">=2.*", "^", "1.0 -", ">=1.0 ||"} {
for _, constraint := range []string{">=2.*", "^", "~x-dev", "1.0 -", ">=1.0 ||"} {
if _, err := ParseNative(constraint, schemeComposer); err == nil {
t.Errorf("ParseNative(%q, composer) succeeded", constraint)
}
Expand Down Expand Up @@ -113,3 +113,27 @@ func TestComposerAndPubVersRoundTrip(t *testing.T) {
}
}
}

func TestComposerDevWildcardRanges(t *testing.T) {
tests := []struct {
constraint string
version string
want bool
}{
{constraint: "~2.x-dev", version: "2.9999999.9999999.9999999-dev", want: true},
{constraint: "~2.x-dev", version: "3.0.0.0-dev", want: false},
{constraint: "~2.0.x-dev", version: "2.1.0.0-dev", want: false},
{constraint: "^2.0.x-dev", version: "2.0.9999999.9999999-dev", want: true},
{constraint: "^2.0.x-dev", version: "3.0.0.0-dev", want: false},
{constraint: "2.x-dev - 3.x-dev", version: "3.9999999.9999999.9999999-dev", want: true},
}
for _, test := range tests {
r, err := ParseNative(test.constraint, schemeComposer)
if err != nil {
t.Fatalf("ParseNative(%q, composer): %v", test.constraint, err)
}
if got := r.Contains(test.version); got != test.want {
t.Errorf("ParseNative(%q, composer).Contains(%q) = %v, want %v", test.constraint, test.version, got, test.want)
}
}
}
Loading