diff --git a/src/semver/SemverObject.zig b/src/semver/SemverObject.zig index af34c99d62..e3da766f34 100644 --- a/src/semver/SemverObject.zig +++ b/src/semver/SemverObject.zig @@ -395,19 +395,19 @@ pub fn parse(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSE // Format version string var version_str = std.ArrayList(u8).init(allocator); defer version_str.deinit(); - + try version_str.writer().print("{d}.{d}.{d}", .{ version.major orelse 0, version.minor orelse 0, version.patch orelse 0 }); - + if (version.tag.hasPre()) { try version_str.append('-'); try version_str.appendSlice(version.tag.pre.slice(version_slice.slice())); } - + if (version.tag.hasBuild()) { try version_str.append('+'); try version_str.appendSlice(version.tag.build.slice(version_slice.slice())); } - + obj.put(globalThis, JSC.ZigString.static("version"), bun.String.createUTF8ForJS(globalThis, version_str.items)); // Store raw input @@ -450,7 +450,7 @@ pub fn bump(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSEr var identifier_slice: JSC.ZigString.Slice = .empty; defer identifier_slice.deinit(); - + if (arguments.len > 2 and !arguments[2].isUndefinedOrNull()) { const id_str = try arguments[2].toJSString(globalThis); identifier_slice = id_str.toSlice(globalThis, allocator); @@ -458,7 +458,7 @@ pub fn bump(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSEr const new_version_str = parse_result.version.min().bump(allocator, release_type, identifier_slice.slice(), version_slice.slice()) catch return JSC.JSValue.null; defer allocator.free(new_version_str); - + return bun.String.createUTF8ForJS(globalThis, new_version_str); } @@ -498,10 +498,6 @@ pub fn intersects(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bu return JSC.jsBoolean(g1.intersects(&g2, r1_slice.slice(), r2_slice.slice())); } - - - - fn findSatisfyingVersion( globalThis: *JSC.JSGlobalObject, versions_array: JSC.JSValue, @@ -606,8 +602,6 @@ pub fn minSatisfying(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) return findSatisfyingVersion(globalThis, versions_array, range_slice.slice(), allocator, false); } - - pub fn simplifyRange(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue { var arena = std.heap.ArenaAllocator.init(bun.default_allocator); defer arena.deinit(); @@ -639,7 +633,7 @@ pub fn simplifyRange(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) // Collect all versions that satisfy the range var satisfying_versions = std.ArrayList(Version).init(allocator); defer satisfying_versions.deinit(); - + const length = versions_array.getLength(globalThis) catch return arguments[1]; if (length == 0) return arguments[1]; @@ -647,7 +641,7 @@ pub fn simplifyRange(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) while (i < length) : (i += 1) { const item = versions_array.getIndex(globalThis, i) catch continue; if (!item.isString()) continue; - + const version_string = try item.toJSString(globalThis); const version_slice = version_string.toSlice(globalThis, allocator); defer version_slice.deinit(); @@ -674,33 +668,33 @@ pub fn simplifyRange(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) // Try to find a simpler range const simplified = try findSimplifiedRange(allocator, satisfying_versions.items, range_slice.slice()); - + // If the simplified range is shorter, return it if (simplified.len < range_slice.slice().len) { return bun.String.createUTF8ForJS(globalThis, simplified); } - + // Otherwise return the original range return arguments[1]; } fn findSimplifiedRange(allocator: std.mem.Allocator, versions: []const Version, original_range: []const u8) ![]const u8 { if (versions.len == 0) return original_range; - + const first = versions[0]; const last = versions[versions.len - 1]; - + // Check if all versions are in the same major version var same_major = true; var same_minor = true; var same_patch = true; - + for (versions) |v| { if (v.major != first.major) same_major = false; if (v.minor != first.minor) same_minor = false; if (v.patch != first.patch) same_patch = false; } - + // If all versions are the same, return exact version only if it's shorter // AND we have multiple versions (not just one that happened to match) if (same_patch and same_minor and same_major and versions.len > 1) { @@ -709,7 +703,7 @@ fn findSimplifiedRange(allocator: std.mem.Allocator, versions: []const Version, return exact; } } - + // Check for consecutive patch versions in same minor if (same_major and same_minor) { var consecutive = true; @@ -721,19 +715,19 @@ fn findSimplifiedRange(allocator: std.mem.Allocator, versions: []const Version, } expected_patch += 1; } - + if (consecutive and versions.len >= 3) { // Use tilde range for consecutive patches return try std.fmt.allocPrint(allocator, "~{d}.{d}.{d}", .{ first.major, first.minor, first.patch }); } } - + // Check for consecutive minor versions in same major if (same_major) { // Check if we have all minors from first to last var expected_versions = std.ArrayList(Version).init(allocator); defer expected_versions.deinit(); - + var current_minor = first.minor; while (current_minor <= last.minor) : (current_minor += 1) { var current_patch: u32 = 0; @@ -746,48 +740,48 @@ fn findSimplifiedRange(allocator: std.mem.Allocator, versions: []const Version, } } } - + // If we have good coverage, use caret range if (expected_versions.items.len >= versions.len * 3 / 4) { // 75% coverage return try std.fmt.allocPrint(allocator, "^{d}.{d}.{d}", .{ first.major, first.minor, first.patch }); } } - + // Try range format only if it makes sense (versions are close together) if (versions.len > 1) { // Only use range format if versions are reasonably close const major_diff = last.major - first.major; const is_close = major_diff <= 1; - + if (is_close) { const range_str = try std.fmt.allocPrint(allocator, ">={d}.{d}.{d} <={d}.{d}.{d}", .{ first.major, first.minor, first.patch, - last.major, last.minor, last.patch, + last.major, last.minor, last.patch, }); - + if (range_str.len < original_range.len) { return range_str; } } } - + // If we have just a few versions, try OR'ing them if (versions.len <= 3) { var result = std.ArrayList(u8).init(allocator); defer result.deinit(); - + for (versions, 0..) |v, idx| { if (idx > 0) { try result.appendSlice(" || "); } try result.writer().print("{d}.{d}.{d}", .{ v.major, v.minor, v.patch }); } - + if (result.items.len < original_range.len) { return try allocator.dupe(u8, result.items); } } - + // Return original if we can't simplify return original_range; } diff --git a/src/semver/SemverQuery.zig b/src/semver/SemverQuery.zig index fe696ef87f..ea934db0bb 100644 --- a/src/semver/SemverQuery.zig +++ b/src/semver/SemverQuery.zig @@ -101,7 +101,7 @@ pub const List = struct { pub fn intersects(list1: *const List, list2: *const List, list1_buf: string, list2_buf: string) bool { // Two lists intersect if their ANDed queries can be satisfied by some version // This is an OR operation - if any of the ORed items intersect, the lists intersect - + var l1 = list1; while (true) { var l2 = list2; @@ -113,7 +113,7 @@ pub const List = struct { } l1 = l1.next orelse break; } - + return false; } @@ -380,28 +380,28 @@ pub fn satisfiesPre(query: *const Query, version: Version, query_buf: string, ve pub fn intersects(query1: *const Query, query2: *const Query, query1_buf: string, query2_buf: string) bool { // Two queries (ANDed ranges) intersect if there exists any version that satisfies both // We need to check if ALL constraints from both queries can be satisfied simultaneously - + // Collect all ranges from both queries var all_ranges = std.ArrayList(Range).init(bun.default_allocator); defer all_ranges.deinit(); - + // Add all ranges from query1 var q1: ?*const Query = query1; while (q1) |q| { all_ranges.append(q.range) catch return false; q1 = q.next; } - + // Add all ranges from query2 var q2: ?*const Query = query2; while (q2) |q| { all_ranges.append(q.range) catch return false; q2 = q.next; } - + // Now check if all these ranges can be satisfied simultaneously // This means we need to find if there's a non-empty intersection of all ranges - + // Find the effective lower and upper bounds from all ranges var has_lower = false; var lower_version: Version = undefined; @@ -409,7 +409,7 @@ pub fn intersects(query1: *const Query, query2: *const Query, query1_buf: string var has_upper = false; var upper_version: Version = undefined; var upper_inclusive = false; - + for (all_ranges.items) |range| { // Process lower bounds if (range.hasLeft()) { @@ -429,7 +429,7 @@ pub fn intersects(query1: *const Query, query2: *const Query, query1_buf: string } else if (range.left.op == .eql and !range.hasRight()) { // Exact version constraint const exact_version = range.left.version; - + // Check if this exact version satisfies all other constraints for (all_ranges.items) |other_range| { if (!other_range.satisfies(exact_version, query1_buf, query2_buf)) { @@ -439,7 +439,7 @@ pub fn intersects(query1: *const Query, query2: *const Query, query1_buf: string return true; } } - + // Process upper bounds if (range.hasRight()) { if (range.right.op == .lte or range.right.op == .lt) { @@ -472,14 +472,14 @@ pub fn intersects(query1: *const Query, query2: *const Query, query1_buf: string } } } - + // Check if the effective range is valid if (has_lower and has_upper) { const order = lower_version.orderWithoutBuild(upper_version, "", ""); if (order == .gt) return false; if (order == .eq and (!lower_inclusive or !upper_inclusive)) return false; } - + // If we get here, the ranges can intersect return true; } diff --git a/src/semver/SemverRange.zig b/src/semver/SemverRange.zig index 5696d24984..5d701d2d2e 100644 --- a/src/semver/SemverRange.zig +++ b/src/semver/SemverRange.zig @@ -247,26 +247,26 @@ pub fn satisfiesPre(range: Range, version: Version, range_buf: string, version_b } pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string, range2_buf: string) bool { - + // If either range has no constraints, they intersect if (!range1.hasLeft() or !range2.hasLeft()) { return true; } - + // Special case: if either range accepts any version (>= 0.0.0), they intersect if (range1.anyRangeSatisfies() or range2.anyRangeSatisfies()) { return true; } - + // For two ranges to intersect, there must exist at least one version that satisfies both // We need to check if the ranges overlap by examining their bounds - + // First, let's handle exact version matches (single comparator with op == eql) if (range1.left.op == .eql and !range1.hasRight() and range2.left.op == .eql and !range2.hasRight()) { // Both are exact versions, they only intersect if they're the same return range1.left.version.eql(range2.left.version); } - + // If one is an exact version and the other is a range, check if the exact version satisfies the range if (range1.left.op == .eql and !range1.hasRight()) { // range1 is an exact version, check if it satisfies range2 @@ -276,11 +276,11 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string // range2 is an exact version, check if it satisfies range1 return range1.satisfies(range2.left.version, range1_buf, range2_buf); } - + // Now handle general ranges // Two ranges intersect if their intervals overlap // We need to find the effective lower and upper bounds of each range - + // For range1 var r1_has_lower = false; var r1_lower_version: Version = undefined; @@ -288,13 +288,13 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string var r1_has_upper = false; var r1_upper_version: Version = undefined; var r1_upper_inclusive = false; - + if (range1.left.op == .gte or range1.left.op == .gt) { r1_has_lower = true; r1_lower_version = range1.left.version; r1_lower_inclusive = (range1.left.op == .gte); } - + if (range1.hasRight()) { if (range1.right.op == .lte or range1.right.op == .lt) { r1_has_upper = true; @@ -307,7 +307,7 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string r1_upper_version = range1.left.version; r1_upper_inclusive = (range1.left.op == .lte); } - + // For range2 var r2_has_lower = false; var r2_lower_version: Version = undefined; @@ -315,13 +315,13 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string var r2_has_upper = false; var r2_upper_version: Version = undefined; var r2_upper_inclusive = false; - + if (range2.left.op == .gte or range2.left.op == .gt) { r2_has_lower = true; r2_lower_version = range2.left.version; r2_lower_inclusive = (range2.left.op == .gte); } - + if (range2.hasRight()) { if (range2.right.op == .lte or range2.right.op == .lt) { r2_has_upper = true; @@ -334,7 +334,7 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string r2_upper_version = range2.left.version; r2_upper_inclusive = (range2.left.op == .lte); } - + // Check if the ranges overlap // Case 1: Both have lower and upper bounds if (r1_has_lower and r1_has_upper and r2_has_lower and r2_has_upper) { @@ -342,15 +342,15 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string const r1_upper_vs_r2_lower = r1_upper_version.orderWithoutBuild(r2_lower_version, "", ""); if (r1_upper_vs_r2_lower == .lt) return false; if (r1_upper_vs_r2_lower == .eq and (!r1_upper_inclusive or !r2_lower_inclusive)) return false; - + // Check if r2's upper is below r1's lower const r2_upper_vs_r1_lower = r2_upper_version.orderWithoutBuild(r1_lower_version, "", ""); if (r2_upper_vs_r1_lower == .lt) return false; if (r2_upper_vs_r1_lower == .eq and (!r2_upper_inclusive or !r1_lower_inclusive)) return false; - + return true; } - + // Case 2: One or both ranges are unbounded on one side if (r1_has_lower and r2_has_upper) { // Check if r2's upper is below r1's lower @@ -358,14 +358,14 @@ pub fn intersects(range1: *const Range, range2: *const Range, range1_buf: string if (order == .lt) return false; if (order == .eq and (!r2_upper_inclusive or !r1_lower_inclusive)) return false; } - + if (r2_has_lower and r1_has_upper) { // Check if r1's upper is below r2's lower const order = r1_upper_version.orderWithoutBuild(r2_lower_version, "", ""); if (order == .lt) return false; if (order == .eq and (!r1_upper_inclusive or !r2_lower_inclusive)) return false; } - + // If we get here, the ranges intersect return true; } diff --git a/src/semver/Version.zig b/src/semver/Version.zig index d0ce54a646..876a43b5f8 100644 --- a/src/semver/Version.zig +++ b/src/semver/Version.zig @@ -286,12 +286,12 @@ pub const Version = extern struct { prepatch, prerelease, release, - + pub fn fromString(s: []const u8) ?ReleaseType { return std.meta.stringToEnum(ReleaseType, s); } }; - + pub fn bump( self: Version, allocator: std.mem.Allocator, @@ -352,7 +352,7 @@ pub const Version = extern struct { } else { // Increment existing prerelease const existing_pre = self.tag.pre.slice(original_buf); - + // Find last numeric component var last_dot: ?usize = null; var i: usize = existing_pre.len; @@ -362,7 +362,7 @@ pub const Version = extern struct { break; } } - + if (last_dot) |dot_pos| { const last_part = existing_pre[dot_pos + 1 ..]; if (std.fmt.parseUnsigned(u64, last_part, 10) catch null) |num| { @@ -386,14 +386,14 @@ pub const Version = extern struct { // Build the final version string var output = std.ArrayList(u8).init(allocator); errdefer output.deinit(); - + try output.writer().print("{d}.{d}.{d}", .{ new_version.major, new_version.minor, new_version.patch }); - + if (new_version.tag.hasPre()) { try output.append('-'); try output.appendSlice(new_version.tag.pre.slice(pre_strings.items)); } - + return output.toOwnedSlice(); } @@ -742,7 +742,7 @@ pub const Version = extern struct { if (tag_str.len == 0) { return JSC.JSValue.null; } - + const array = try JSC.JSValue.createEmptyArray(globalThis, 0); var it = strings.split(tag_str, ".");