github.com/google/osv-scalibr@v0.4.1/semantic/version-semver.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package semantic 16 17 import ( 18 "strings" 19 ) 20 21 // Removes build metadata from the given string if present, per semver v2 22 // 23 // See https://semver.org/spec/v2.0.0.html#spec-item-10 24 func removeBuildMetadata(str string) string { 25 parts := strings.Split(str, "+") 26 27 return parts[0] 28 } 29 30 func compareBuildComponents(a, b string) int { 31 // https://semver.org/spec/v2.0.0.html#spec-item-10 32 a = removeBuildMetadata(a) 33 b = removeBuildMetadata(b) 34 35 // the spec doesn't explicitly say "don't include the hyphen in the compare" 36 // but it's what node-semver does so for now let's go with that... 37 a = strings.TrimPrefix(a, "-") 38 b = strings.TrimPrefix(b, "-") 39 40 // versions with a prerelease are considered less than those without 41 // https://semver.org/spec/v2.0.0.html#spec-item-9 42 if a == "" && b != "" { 43 return +1 44 } 45 if a != "" && b == "" { 46 return -1 47 } 48 49 return compareSemverBuildComponents( 50 strings.Split(a, "."), 51 strings.Split(b, "."), 52 ) 53 } 54 55 func compareSemverBuildComponents(a, b []string) int { 56 minComponentLength := min(len(a), len(b)) 57 58 var compare int 59 60 for i := range minComponentLength { 61 ai, aErr := convertToBigInt(a[i]) 62 bi, bErr := convertToBigInt(b[i]) 63 64 switch { 65 // 1. Identifiers consisting of only digits are compared numerically. 66 case aErr == nil && bErr == nil: 67 compare = ai.Cmp(bi) 68 // 2. Identifiers with letters or hyphens are compared lexically in ASCII sort order. 69 case aErr != nil && bErr != nil: 70 compare = strings.Compare(a[i], b[i]) 71 // 3. Numeric identifiers always have lower precedence than non-numeric identifiers. 72 case aErr == nil: 73 compare = -1 74 default: 75 compare = +1 76 } 77 78 if compare != 0 { 79 if compare > 0 { 80 return 1 81 } 82 83 return -1 84 } 85 } 86 87 // 4. A larger set of pre-release fields has a higher precedence than a smaller set, 88 // if all the preceding identifiers are equal. 89 if len(a) > len(b) { 90 return +1 91 } 92 if len(a) < len(b) { 93 return -1 94 } 95 96 return 0 97 } 98 99 type semverVersion struct { 100 semverLikeVersion 101 } 102 103 func parseSemverVersion(str string) semverVersion { 104 return semverVersion{parseSemverLikeVersion(str, 3)} 105 } 106 107 func (v semverVersion) compare(w semverVersion) int { 108 if diff := v.Components.Cmp(w.Components); diff != 0 { 109 return diff 110 } 111 112 return compareBuildComponents(v.Build, w.Build) 113 } 114 115 func (v semverVersion) CompareStr(str string) (int, error) { 116 return v.compare(parseSemverVersion(str)), nil 117 }