mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 07:28:53 +00:00
Compare commits
28 Commits
dylan/pyth
...
claude/com
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
600d8da8c2 | ||
|
|
686523b4ea | ||
|
|
762a9f4788 | ||
|
|
bdba56633d | ||
|
|
d52d3fec93 | ||
|
|
0d6bf617bc | ||
|
|
b9ae197930 | ||
|
|
b4eed4e0df | ||
|
|
6cf322556a | ||
|
|
04888224da | ||
|
|
710d39f88e | ||
|
|
18625cb933 | ||
|
|
c0dc4f8cbc | ||
|
|
b1d8e392b8 | ||
|
|
3da082ef2c | ||
|
|
2f4a72688b | ||
|
|
556f4d3746 | ||
|
|
bafa1f2efe | ||
|
|
5133cce905 | ||
|
|
97ec01cb7b | ||
|
|
d9b046e76d | ||
|
|
4b067568c4 | ||
|
|
3b7841f5f6 | ||
|
|
8fb8fc3287 | ||
|
|
becddb5359 | ||
|
|
373ab8d53a | ||
|
|
d1e817fcce | ||
|
|
1d24744ecb |
@@ -138,7 +138,7 @@ pub fn runWithBody(ctx: *ErrorReportRequest, body: []const u8, r: AnyResponse) !
|
||||
// So we can know if the frame is inside the HMR runtime if
|
||||
// `frame.position.line < generated_mappings[1].lines`.
|
||||
const generated_mappings = result.mappings.generated();
|
||||
if (generated_mappings.len <= 1 or frame.position.line.zeroBased() < generated_mappings[1].lines.zeroBased()) {
|
||||
if (generated_mappings.len <= 1 or (generated_mappings.len > 1 and frame.position.line.zeroBased() < generated_mappings[1].lines.zeroBased())) {
|
||||
frame.source_url = .init(runtime_name); // matches value in source map
|
||||
frame.position = .invalid;
|
||||
continue;
|
||||
|
||||
@@ -459,7 +459,7 @@ pub fn sweepWeakRefs(timer: *EventLoopTimer, now_ts: *const bun.timespec) EventL
|
||||
|
||||
pub const GetResult = struct {
|
||||
index: bun.GenericIndex(u32, Entry),
|
||||
mappings: SourceMap.Mapping.List,
|
||||
mappings: SourceMap.MappingsData,
|
||||
file_paths: []const []const u8,
|
||||
entry_files: *const bun.MultiArrayList(PackedMap.RefOrEmpty),
|
||||
|
||||
|
||||
@@ -79,6 +79,95 @@ pub const SavedMappings = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Compact variant that uses LineOffsetTable.Compact for reduced memory usage
|
||||
pub const SavedMappingsCompact = struct {
|
||||
compact_table: *SourceMap.LineOffsetTable.Compact,
|
||||
sources_count: usize,
|
||||
|
||||
pub fn init(allocator: Allocator, vlq_mappings: []const u8) !*SavedMappingsCompact {
|
||||
return bun.new(SavedMappingsCompact, .{
|
||||
.compact_table = try SourceMap.LineOffsetTable.Compact.init(allocator, vlq_mappings),
|
||||
.sources_count = 1, // Default to 1 source
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(this: *SavedMappingsCompact) void {
|
||||
this.compact_table.deref();
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
fn toCompactMappings(this: *SavedMappingsCompact) anyerror!ParsedSourceMap {
|
||||
// For error reporting and other uses, stay in compact format
|
||||
// Increment ref count and return compact data
|
||||
this.compact_table.ref();
|
||||
|
||||
// Calculate proper input line count - use the last line index as the total
|
||||
const input_line_count = if (this.compact_table.line_offsets().len() > 1)
|
||||
this.compact_table.line_offsets().len() - 1
|
||||
else
|
||||
1;
|
||||
|
||||
return ParsedSourceMap{
|
||||
.ref_count = .init(),
|
||||
.input_line_count = input_line_count,
|
||||
.mappings = .{ .compact = this.compact_table },
|
||||
.external_source_names = &.{},
|
||||
.underlying_provider = .none,
|
||||
.is_standalone_module_graph = false,
|
||||
};
|
||||
}
|
||||
|
||||
fn toFullMappings(this: *SavedMappingsCompact, allocator: Allocator, path: string) anyerror!ParsedSourceMap {
|
||||
const line_offsets = this.compact_table.line_offsets();
|
||||
// Parse the VLQ mappings using the existing parser for coverage analysis
|
||||
const input_line_count = if (line_offsets.len() > 1)
|
||||
line_offsets.len() - 1
|
||||
else
|
||||
1;
|
||||
|
||||
const result = SourceMap.Mapping.parse(
|
||||
allocator,
|
||||
this.compact_table.vlq_mappings,
|
||||
null, // estimated mapping count
|
||||
@intCast(this.sources_count), // use stored sources count
|
||||
input_line_count, // input line count - use proper calculation
|
||||
.{ .allow_names = true, .sort = true }, // Enable names support for coverage
|
||||
);
|
||||
switch (result) {
|
||||
.fail => |fail| {
|
||||
if (Output.enable_ansi_colors_stderr) {
|
||||
try fail.toData(path).writeFormat(
|
||||
Output.errorWriter(),
|
||||
logger.Kind.warn,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
try fail.toData(path).writeFormat(
|
||||
Output.errorWriter(),
|
||||
logger.Kind.warn,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
return fail.err;
|
||||
},
|
||||
.success => |success| {
|
||||
return success;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toMapping(this: *SavedMappingsCompact, allocator: Allocator, path: string) anyerror!ParsedSourceMap {
|
||||
// Check if coverage is enabled - only then convert to full format
|
||||
if (bun.cli.Command.get().test_options.coverage.enabled) {
|
||||
return this.toFullMappings(allocator, path);
|
||||
} else {
|
||||
return this.toCompactMappings();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// ParsedSourceMap is the canonical form for sourcemaps,
|
||||
///
|
||||
/// but `SavedMappings` and `SourceProviderMap` are much cheaper to construct.
|
||||
@@ -86,6 +175,7 @@ pub const SavedMappings = struct {
|
||||
pub const Value = bun.TaggedPointerUnion(.{
|
||||
ParsedSourceMap,
|
||||
SavedMappings,
|
||||
SavedMappingsCompact,
|
||||
SourceProviderMap,
|
||||
BakeSourceProvider,
|
||||
});
|
||||
@@ -142,6 +232,20 @@ pub fn onSourceMapChunk(this: *SavedSourceMap, chunk: SourceMap.Chunk, source: *
|
||||
|
||||
pub const SourceMapHandler = js_printer.SourceMapHandler.For(SavedSourceMap, onSourceMapChunk);
|
||||
|
||||
fn deinitValue(value: Value) void {
|
||||
if (value.get(ParsedSourceMap)) |source_map| {
|
||||
source_map.deref();
|
||||
} else if (value.get(SavedMappings)) |saved_mappings| {
|
||||
var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(saved_mappings)) };
|
||||
saved.deinit();
|
||||
} else if (value.get(SavedMappingsCompact)) |saved_compact| {
|
||||
var compact: *SavedMappingsCompact = saved_compact;
|
||||
compact.deinit();
|
||||
} else if (value.get(SourceProviderMap)) |provider| {
|
||||
_ = provider; // do nothing, we did not hold a ref to ZigSourceProvider
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(this: *SavedSourceMap) void {
|
||||
{
|
||||
this.lock();
|
||||
@@ -149,15 +253,8 @@ pub fn deinit(this: *SavedSourceMap) void {
|
||||
|
||||
var iter = this.map.valueIterator();
|
||||
while (iter.next()) |val| {
|
||||
var value = Value.from(val.*);
|
||||
if (value.get(ParsedSourceMap)) |source_map| {
|
||||
source_map.deref();
|
||||
} else if (value.get(SavedMappings)) |saved_mappings| {
|
||||
var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(saved_mappings)) };
|
||||
saved.deinit();
|
||||
} else if (value.get(SourceProviderMap)) |provider| {
|
||||
_ = provider; // do nothing, we did not hold a ref to ZigSourceProvider
|
||||
}
|
||||
const value = Value.from(val.*);
|
||||
deinitValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +263,22 @@ pub fn deinit(this: *SavedSourceMap) void {
|
||||
}
|
||||
|
||||
pub fn putMappings(this: *SavedSourceMap, source: *const logger.Source, mappings: MutableString) !void {
|
||||
try this.putValue(source.path.text, Value.init(bun.cast(*SavedMappings, try bun.default_allocator.dupe(u8, mappings.list.items))));
|
||||
// Check if coverage is enabled - if so, use full format for easier unpacking
|
||||
// Otherwise use compact format for memory savings
|
||||
if (bun.cli.Command.get().test_options.coverage.enabled) {
|
||||
const data = try bun.default_allocator.dupe(u8, mappings.list.items);
|
||||
try this.putValue(source.path.text, Value.init(bun.cast(*SavedMappings, data.ptr)));
|
||||
} else {
|
||||
// The mappings buffer has a 24-byte header when prepend_count is true
|
||||
// We need to skip this header for the compact format
|
||||
const vlq_data = if (mappings.list.items.len > vlq_offset)
|
||||
mappings.list.items[vlq_offset..]
|
||||
else
|
||||
mappings.list.items;
|
||||
|
||||
const compact = try SavedMappingsCompact.init(bun.default_allocator, vlq_data);
|
||||
try this.putValue(source.path.text, Value.init(compact));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn putValue(this: *SavedSourceMap, path: []const u8, value: Value) !void {
|
||||
@@ -175,16 +287,8 @@ pub fn putValue(this: *SavedSourceMap, path: []const u8, value: Value) !void {
|
||||
|
||||
const entry = try this.map.getOrPut(bun.hash(path));
|
||||
if (entry.found_existing) {
|
||||
var old_value = Value.from(entry.value_ptr.*);
|
||||
if (old_value.get(ParsedSourceMap)) |parsed_source_map| {
|
||||
var source_map: *ParsedSourceMap = parsed_source_map;
|
||||
source_map.deref();
|
||||
} else if (old_value.get(SavedMappings)) |saved_mappings| {
|
||||
var saved = SavedMappings{ .data = @as([*]u8, @ptrCast(saved_mappings)) };
|
||||
saved.deinit();
|
||||
} else if (old_value.get(SourceProviderMap)) |provider| {
|
||||
_ = provider; // do nothing, we did not hold a ref to ZigSourceProvider
|
||||
}
|
||||
const old_value = Value.from(entry.value_ptr.*);
|
||||
deinitValue(old_value);
|
||||
}
|
||||
entry.value_ptr.* = value.ptr();
|
||||
}
|
||||
@@ -226,6 +330,20 @@ fn getWithContent(
|
||||
|
||||
return .{ .map = result };
|
||||
},
|
||||
@field(Value.Tag, @typeName(SavedMappingsCompact)) => {
|
||||
defer this.unlock();
|
||||
var saved_compact = Value.from(mapping.value_ptr.*).as(SavedMappingsCompact);
|
||||
const parsed_map = saved_compact.toMapping(bun.default_allocator, path) catch {
|
||||
// On failure, remove the entry and return null to indicate no sourcemap
|
||||
_ = this.map.remove(mapping.key_ptr.*);
|
||||
return .{};
|
||||
};
|
||||
const result = bun.new(ParsedSourceMap, parsed_map);
|
||||
mapping.value_ptr.* = Value.init(result).ptr();
|
||||
result.ref();
|
||||
|
||||
return .{ .map = result };
|
||||
},
|
||||
@field(Value.Tag, @typeName(SourceProviderMap)) => {
|
||||
const ptr: *SourceProviderMap = Value.from(mapping.value_ptr.*).as(SourceProviderMap);
|
||||
this.unlock();
|
||||
|
||||
@@ -3,3 +3,4 @@ pub const BabyList = @import("./collections/baby_list.zig").BabyList;
|
||||
pub const OffsetList = @import("./collections/baby_list.zig").OffsetList;
|
||||
pub const bit_set = @import("./collections/bit_set.zig");
|
||||
pub const HiveArray = @import("./collections/hive_array.zig").HiveArray;
|
||||
pub const IndexArrayList = @import("./collections/index_array_list.zig").IndexArrayList;
|
||||
|
||||
145
src/collections/index_array_list.zig
Normal file
145
src/collections/index_array_list.zig
Normal file
@@ -0,0 +1,145 @@
|
||||
/// A space-efficient array list for storing indices that automatically
|
||||
/// chooses the smallest integer type needed to represent all values.
|
||||
///
|
||||
/// This data structure optimizes memory usage by starting with u8 storage
|
||||
/// and dynamically upgrading to larger integer types (u16, u24, u32) only
|
||||
/// when values exceed the current type's range. This is particularly useful
|
||||
/// for storing indices, offsets, or other non-negative integers where the
|
||||
/// maximum value is not known in advance.
|
||||
///
|
||||
/// Features:
|
||||
/// - Automatic type promotion: starts with u8, upgrades to u16/u24/u32 as needed
|
||||
/// - Memory efficient: uses the smallest possible integer type for the data
|
||||
/// - Zero-cost abstractions: no runtime overhead for type checking once established
|
||||
/// - Compatible with standard ArrayList operations
|
||||
///
|
||||
/// Use cases:
|
||||
/// - Source map line/column mappings where most values are small
|
||||
/// - Array indices where the array size grows dynamically
|
||||
/// - Offset tables where most offsets fit in smaller types
|
||||
/// - Any scenario where you're storing many small integers with occasional large values
|
||||
///
|
||||
/// Example:
|
||||
/// ```zig
|
||||
/// var list = IndexArrayList.init(allocator, 16);
|
||||
/// try list.append(allocator, 10); // stored as u8
|
||||
/// try list.append(allocator, 300); // upgrades to u16, copies existing data
|
||||
/// try list.append(allocator, 70000); // upgrades to u32, copies existing data
|
||||
/// ```
|
||||
///
|
||||
/// Memory layout transitions:
|
||||
/// - Initial: u8 array (1 byte per element)
|
||||
/// - After value > 255: u16 array (2 bytes per element)
|
||||
/// - After value > 65535: u24 array (3 bytes per element)
|
||||
/// - After value > 16777215: u32 array (4 bytes per element)
|
||||
///
|
||||
/// Note: u24 is used as an intermediate step to save memory when values
|
||||
/// fit in 24 bits but exceed 16 bits, which is common in large source maps.
|
||||
pub const IndexArrayList = union(Size) {
|
||||
u8: bun.BabyList(u8),
|
||||
u16: bun.BabyList(u16),
|
||||
u24: bun.BabyList(u24),
|
||||
u32: bun.BabyList(u32),
|
||||
|
||||
pub const empty = IndexArrayList{ .u8 = .{} };
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, initial_capacity: usize) !IndexArrayList {
|
||||
return .{ .u8 = try bun.BabyList(u8).initCapacity(allocator, initial_capacity) };
|
||||
}
|
||||
|
||||
fn copyTIntoT2(comptime T1: type, src: []const T1, comptime T2: type, dst: []T2) void {
|
||||
for (src, dst) |item, *dest| {
|
||||
dest.* = @intCast(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub const Slice = union(Size) {
|
||||
u8: []const u8,
|
||||
u16: []const u16,
|
||||
u24: []const u24,
|
||||
u32: []const u32,
|
||||
|
||||
pub fn len(self: Slice) usize {
|
||||
return switch (self) {
|
||||
.u8 => self.u8.len,
|
||||
.u16 => self.u16.len,
|
||||
.u24 => self.u24.len,
|
||||
.u32 => self.u32.len,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn items(self: *const IndexArrayList) Slice {
|
||||
return switch (self.*) {
|
||||
.u8 => |*list| .{ .u8 = list.sliceConst() },
|
||||
.u16 => |*list| .{ .u16 = list.sliceConst() },
|
||||
.u24 => |*list| .{ .u24 = list.sliceConst() },
|
||||
.u32 => |*list| .{ .u32 = list.sliceConst() },
|
||||
};
|
||||
}
|
||||
|
||||
fn upconvert(self: *IndexArrayList, allocator: std.mem.Allocator, to: Size) !void {
|
||||
switch (self.*) {
|
||||
inline else => |*current, current_size| {
|
||||
switch (to) {
|
||||
inline else => |to_size| {
|
||||
const Type = Size.Type(to_size);
|
||||
var new_list = try bun.BabyList(Type).initCapacity(allocator, current.len + 1);
|
||||
new_list.len = current.len;
|
||||
copyTIntoT2(current_size.Type(), current.sliceConst(), Type, new_list.slice());
|
||||
self.deinit(allocator);
|
||||
self.* = @unionInit(IndexArrayList, @tagName(to_size), new_list);
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(self: *IndexArrayList, allocator: std.mem.Allocator, value: u32) !void {
|
||||
const target_size: Size = switch (value) {
|
||||
std.math.minInt(u8)...std.math.maxInt(u8) => .u8,
|
||||
std.math.maxInt(u8) + 1...std.math.maxInt(u16) => .u16,
|
||||
std.math.maxInt(u16) + 1...std.math.maxInt(u24) => .u24,
|
||||
std.math.maxInt(u24) + 1...std.math.maxInt(u32) => .u32,
|
||||
};
|
||||
|
||||
if (@intFromEnum(target_size) > @intFromEnum(@as(Size, self.*))) {
|
||||
try self.upconvert(allocator, target_size);
|
||||
}
|
||||
|
||||
switch (self.*) {
|
||||
.u8 => |*list| try list.append(allocator, &[_]u8{@intCast(value)}),
|
||||
.u16 => |*list| try list.append(allocator, &[_]u16{@intCast(value)}),
|
||||
.u24 => |*list| try list.append(allocator, &[_]u24{@intCast(value)}),
|
||||
.u32 => |*list| try list.append(allocator, &[_]u32{@intCast(value)}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *IndexArrayList, allocator: std.mem.Allocator) void {
|
||||
switch (self.*) {
|
||||
.u8 => |*list| list.deinitWithAllocator(allocator),
|
||||
.u16 => |*list| list.deinitWithAllocator(allocator),
|
||||
.u24 => |*list| list.deinitWithAllocator(allocator),
|
||||
.u32 => |*list| list.deinitWithAllocator(allocator),
|
||||
}
|
||||
}
|
||||
|
||||
const Size = enum(u8) {
|
||||
u8 = 1,
|
||||
u16 = 2,
|
||||
u24 = 3,
|
||||
u32 = 4,
|
||||
|
||||
pub fn Type(self: Size) type {
|
||||
return switch (self) {
|
||||
.u8 => u8,
|
||||
.u16 => u16,
|
||||
.u24 => u24,
|
||||
.u32 => u32,
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
@@ -135,28 +135,13 @@ pub fn constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the VLQ mappings
|
||||
const parse_result = bun.sourcemap.Mapping.parse(
|
||||
bun.default_allocator,
|
||||
mappings_str.slice(),
|
||||
null, // estimated_mapping_count
|
||||
@intCast(sources.items.len), // sources_count
|
||||
std.math.maxInt(i32),
|
||||
.{ .allow_names = true, .sort = true },
|
||||
);
|
||||
|
||||
const mapping_list = switch (parse_result) {
|
||||
.success => |parsed| parsed,
|
||||
.fail => |fail| {
|
||||
if (fail.loc.toNullable()) |loc| {
|
||||
return globalObject.throwValue(globalObject.createSyntaxErrorInstance("{s} at {d}", .{ fail.msg, loc.start }));
|
||||
}
|
||||
return globalObject.throwValue(globalObject.createSyntaxErrorInstance("{s}", .{fail.msg}));
|
||||
},
|
||||
};
|
||||
const compact_sourcemap = try bun.sourcemap.LineOffsetTable.Compact.init(bun.default_allocator, mappings_str.slice());
|
||||
|
||||
const source_map = bun.new(JSSourceMap, .{
|
||||
.sourcemap = bun.new(bun.sourcemap.ParsedSourceMap, mapping_list),
|
||||
.sourcemap = bun.new(bun.sourcemap.ParsedSourceMap, .{
|
||||
.mappings = .{ .compact = compact_sourcemap },
|
||||
.ref_count = .init(),
|
||||
}),
|
||||
.sources = sources.items,
|
||||
.names = names.items,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,228 @@ byte_offset_to_start_of_line: u32 = 0,
|
||||
|
||||
pub const List = bun.MultiArrayList(LineOffsetTable);
|
||||
|
||||
/// Compact variant that keeps VLQ-encoded mappings and line index
|
||||
/// for reduced memory usage vs unpacked MultiArrayList
|
||||
pub const Compact = struct {
|
||||
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
|
||||
pub const ref = RefCount.ref;
|
||||
pub const deref = RefCount.deref;
|
||||
|
||||
/// Thread-safe reference counting for shared access
|
||||
ref_count: RefCount,
|
||||
/// VLQ-encoded sourcemap mappings string
|
||||
vlq_mappings: []const u8,
|
||||
|
||||
/// Index of positions where ';' (line separators) occur in vlq_mappings.
|
||||
/// Lazily populated on first access. Guarded by mutex.
|
||||
lazy_line_offsets: ?bun.collections.IndexArrayList = null,
|
||||
lazy_line_offsets_mutex: bun.Mutex = .{},
|
||||
|
||||
/// Names array for sourcemap symbols
|
||||
names: []const bun.Semver.String,
|
||||
names_buffer: bun.ByteList,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, vlq_mappings: []const u8) !*Compact {
|
||||
const owned_mappings = try allocator.dupe(u8, vlq_mappings);
|
||||
|
||||
return bun.new(Compact, .{
|
||||
.ref_count = .init(),
|
||||
.vlq_mappings = owned_mappings,
|
||||
.names = &[_]bun.Semver.String{},
|
||||
.names_buffer = .{},
|
||||
.allocator = allocator,
|
||||
});
|
||||
}
|
||||
|
||||
fn deinit(self: *Compact) void {
|
||||
self.allocator.free(self.vlq_mappings);
|
||||
if (self.lazy_line_offsets) |*offsets| {
|
||||
offsets.deinit(self.allocator);
|
||||
}
|
||||
self.names_buffer.deinitWithAllocator(self.allocator);
|
||||
self.allocator.free(self.names);
|
||||
bun.destroy(self);
|
||||
}
|
||||
|
||||
pub fn line_offsets(self: *Compact) bun.collections.IndexArrayList.Slice {
|
||||
self.lazy_line_offsets_mutex.lock();
|
||||
defer self.lazy_line_offsets_mutex.unlock();
|
||||
return self.ensureLazyLineOffsets().items();
|
||||
}
|
||||
|
||||
fn ensureLazyLineOffsets(self: *Compact) *const bun.collections.IndexArrayList {
|
||||
if (self.lazy_line_offsets) |*offsets| {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
var offsets = bun.collections.IndexArrayList{ .u8 = .{} };
|
||||
for (self.vlq_mappings, 0..) |char, i| {
|
||||
if (char == ';') {
|
||||
offsets.append(self.allocator, @intCast(i + 1)) catch bun.outOfMemory();
|
||||
}
|
||||
}
|
||||
|
||||
self.lazy_line_offsets = offsets;
|
||||
return &self.lazy_line_offsets.?;
|
||||
}
|
||||
|
||||
/// Find mapping for a given line/column by decoding VLQ with proper global accumulation
|
||||
pub fn findMapping(self: *Compact, target_line: i32, target_column: i32) ?SourceMapping {
|
||||
// VLQ sourcemap spec requires global accumulation for source_index, original_line, original_column
|
||||
// Only generated_column resets per line. We need to process all lines up to target_line
|
||||
// to get correct accumulated state.
|
||||
|
||||
var global_source_index: i32 = 0;
|
||||
var global_original_line: i32 = 0;
|
||||
var global_original_column: i32 = 0;
|
||||
var best_mapping: ?SourceMapping = null;
|
||||
|
||||
// Process all lines from 0 to target_line to maintain correct VLQ accumulation
|
||||
var current_line: i32 = 0;
|
||||
|
||||
switch (self.line_offsets()) {
|
||||
inline else => |offsets| {
|
||||
if (target_line < 0 or offsets.len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we only have one offset (just the initial 0), there's no line data
|
||||
if (offsets.len == 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clamp target_line to valid range
|
||||
const max_line = @as(i32, @intCast(offsets.len - 1));
|
||||
const clamped_target_line = @min(target_line, max_line - 1);
|
||||
|
||||
while (current_line <= clamped_target_line and current_line < max_line) {
|
||||
const line_start = offsets[@intCast(current_line)];
|
||||
const line_end = if (current_line + 1 < max_line)
|
||||
offsets[@intCast(current_line + 1)] - 1 // -1 to exclude the ';'
|
||||
else
|
||||
@as(u32, @intCast(self.vlq_mappings.len));
|
||||
|
||||
if (line_start >= line_end) {
|
||||
current_line += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const line_mappings = self.vlq_mappings[line_start..line_end];
|
||||
|
||||
// generated_column resets to 0 per line (per spec)
|
||||
var generated_column: i32 = 0;
|
||||
var pos: usize = 0;
|
||||
|
||||
while (pos < line_mappings.len) {
|
||||
// Skip commas
|
||||
if (line_mappings[pos] == ',') {
|
||||
pos += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decode generated column delta (resets per line)
|
||||
if (pos >= line_mappings.len) break;
|
||||
const gen_col_result = VLQ.decode(line_mappings, pos);
|
||||
if (gen_col_result.start == pos) break; // Invalid VLQ
|
||||
generated_column += gen_col_result.value;
|
||||
pos = gen_col_result.start;
|
||||
|
||||
// Only process target line for column matching
|
||||
if (current_line == target_line) {
|
||||
// If we've passed the target column, return the last good mapping
|
||||
if (generated_column > target_column and best_mapping != null) {
|
||||
return best_mapping;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos >= line_mappings.len) break;
|
||||
if (line_mappings[pos] == ',') {
|
||||
// Only generated column - no source info, skip
|
||||
pos += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decode source index delta (accumulates globally)
|
||||
if (pos >= line_mappings.len) break;
|
||||
const src_idx_result = VLQ.decode(line_mappings, pos);
|
||||
if (src_idx_result.start == pos) break;
|
||||
global_source_index += src_idx_result.value;
|
||||
pos = src_idx_result.start;
|
||||
|
||||
if (pos >= line_mappings.len) break;
|
||||
|
||||
// Decode original line delta (accumulates globally)
|
||||
if (pos >= line_mappings.len) break;
|
||||
const orig_line_result = VLQ.decode(line_mappings, pos);
|
||||
if (orig_line_result.start == pos) break;
|
||||
global_original_line += orig_line_result.value;
|
||||
pos = orig_line_result.start;
|
||||
|
||||
if (pos >= line_mappings.len) break;
|
||||
|
||||
// Decode original column delta (accumulates globally)
|
||||
if (pos >= line_mappings.len) break;
|
||||
const orig_col_result = VLQ.decode(line_mappings, pos);
|
||||
if (orig_col_result.start == pos) break;
|
||||
global_original_column += orig_col_result.value;
|
||||
pos = orig_col_result.start;
|
||||
|
||||
// Skip name index if present
|
||||
if (pos < line_mappings.len and line_mappings[pos] != ',' and line_mappings[pos] != ';') {
|
||||
if (pos < line_mappings.len) {
|
||||
const name_result = VLQ.decode(line_mappings, pos);
|
||||
if (name_result.start > pos) {
|
||||
pos = name_result.start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update best mapping if this is target line and column is <= target
|
||||
if (current_line == target_line and generated_column <= target_column) {
|
||||
// All values should be non-negative with correct VLQ accumulation
|
||||
if (target_line >= 0 and generated_column >= 0 and
|
||||
global_original_line >= 0 and global_original_column >= 0)
|
||||
{
|
||||
best_mapping = SourceMapping{
|
||||
.generated_line = target_line,
|
||||
.generated_column = generated_column,
|
||||
.source_index = global_source_index,
|
||||
.original_line = global_original_line,
|
||||
.original_column = global_original_column,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current_line += 1;
|
||||
}
|
||||
|
||||
return best_mapping;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get name by index, similar to Mapping.List.getName
|
||||
pub fn getName(self: *const Compact, index: i32) ?[]const u8 {
|
||||
if (index < 0) return null;
|
||||
const i: usize = @intCast(index);
|
||||
|
||||
if (i >= self.names.len) return null;
|
||||
|
||||
const str: *const bun.Semver.String = &self.names[i];
|
||||
return str.slice(self.names_buffer.slice());
|
||||
}
|
||||
|
||||
const SourceMapping = struct {
|
||||
generated_line: i32,
|
||||
generated_column: i32,
|
||||
source_index: i32,
|
||||
original_line: i32,
|
||||
original_column: i32,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn findLine(byte_offsets_to_start_of_line: []const u32, loc: Logger.Loc) i32 {
|
||||
assert(loc.start > -1); // checked by caller
|
||||
var original_line: usize = 0;
|
||||
@@ -223,6 +445,7 @@ pub fn generate(allocator: std.mem.Allocator, contents: []const u8, approximate_
|
||||
return list;
|
||||
}
|
||||
|
||||
const VLQ = @import("./VLQ.zig");
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
@@ -183,7 +183,7 @@ pub fn parseJSON(
|
||||
.fail => |fail| return fail.err,
|
||||
};
|
||||
|
||||
if (hint == .all and hint.all.include_names and map_data.mappings.impl == .with_names) {
|
||||
if (hint == .all and hint.all.include_names and map_data.mappings.list.impl == .with_names) {
|
||||
if (json.get("names")) |names| {
|
||||
if (names.data == .e_array) {
|
||||
var names_list = try std.ArrayListUnmanaged(bun.Semver.String).initCapacity(alloc, names.data.e_array.items.len);
|
||||
@@ -202,8 +202,8 @@ pub fn parseJSON(
|
||||
names_list.appendAssumeCapacity(try bun.Semver.String.initAppendIfNeeded(alloc, &names_buffer, str));
|
||||
}
|
||||
|
||||
map_data.mappings.names = names_list.items;
|
||||
map_data.mappings.names_buffer = .fromList(names_buffer);
|
||||
map_data.mappings.list.names = names_list.items;
|
||||
map_data.mappings.list.names_buffer = .fromList(names_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -830,7 +830,7 @@ pub const Mapping = struct {
|
||||
|
||||
return .{ .success = .{
|
||||
.ref_count = .init(),
|
||||
.mappings = mapping,
|
||||
.mappings = .{ .list = mapping },
|
||||
.input_line_count = input_line_count,
|
||||
} };
|
||||
}
|
||||
@@ -859,6 +859,68 @@ pub const ParseResult = union(enum) {
|
||||
success: ParsedSourceMap,
|
||||
};
|
||||
|
||||
pub const MappingsData = union(enum) {
|
||||
list: Mapping.List,
|
||||
compact: *LineOffsetTable.Compact,
|
||||
|
||||
pub fn find(self: *const MappingsData, line: i32, column: i32) ?Mapping {
|
||||
switch (self.*) {
|
||||
.list => |*list| return list.find(line, column),
|
||||
.compact => |compact| {
|
||||
if (compact.findMapping(line, column)) |sm| {
|
||||
return Mapping{
|
||||
.generated = .{
|
||||
// Values from VLQ are already 0-based
|
||||
.lines = .fromZeroBased(sm.generated_line),
|
||||
.columns = .fromZeroBased(sm.generated_column),
|
||||
},
|
||||
.original = .{
|
||||
.lines = .fromZeroBased(sm.original_line),
|
||||
.columns = .fromZeroBased(sm.original_column),
|
||||
},
|
||||
.source_index = sm.source_index,
|
||||
.name_index = -1, // Compact format doesn't support names yet
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memoryCost(self: *const MappingsData) usize {
|
||||
switch (self.*) {
|
||||
.list => |*list| return list.memoryCost(),
|
||||
.compact => return @sizeOf(*LineOffsetTable.Compact),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *MappingsData, allocator: std.mem.Allocator) void {
|
||||
switch (self.*) {
|
||||
.list => |*list| list.deinit(allocator),
|
||||
.compact => |compact| compact.deref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getName(self: *MappingsData, index: i32) ?[]const u8 {
|
||||
switch (self.*) {
|
||||
.list => |*list| return list.getName(index),
|
||||
.compact => |compact| return compact.getName(index),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generated(self: *const MappingsData) []const LineColumnOffset {
|
||||
switch (self.*) {
|
||||
.list => |*list| return list.generated(),
|
||||
.compact => |_| {
|
||||
// For compact format, we can't provide direct access to generated positions
|
||||
// since they would need to be decoded from VLQ on-demand.
|
||||
// Return empty slice - callers should handle this gracefully or use find() instead
|
||||
return &[_]LineColumnOffset{};
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const ParsedSourceMap = struct {
|
||||
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
|
||||
pub const ref = RefCount.ref;
|
||||
@@ -869,7 +931,7 @@ pub const ParsedSourceMap = struct {
|
||||
ref_count: RefCount,
|
||||
|
||||
input_line_count: usize = 0,
|
||||
mappings: Mapping.List = .{},
|
||||
mappings: MappingsData = .{ .list = .{} },
|
||||
|
||||
/// If this is empty, this implies that the source code is a single file
|
||||
/// transpiled on-demand. If there are items, then it means this is a file
|
||||
@@ -964,34 +1026,42 @@ pub const ParsedSourceMap = struct {
|
||||
}
|
||||
|
||||
pub fn writeVLQs(map: *const ParsedSourceMap, writer: anytype) !void {
|
||||
var last_col: i32 = 0;
|
||||
var last_src: i32 = 0;
|
||||
var last_ol: i32 = 0;
|
||||
var last_oc: i32 = 0;
|
||||
var current_line: i32 = 0;
|
||||
for (
|
||||
map.mappings.generated(),
|
||||
map.mappings.original(),
|
||||
map.mappings.sourceIndex(),
|
||||
0..,
|
||||
) |gen, orig, source_index, i| {
|
||||
if (current_line != gen.lines.zeroBased()) {
|
||||
assert(gen.lines.zeroBased() > current_line);
|
||||
const inc = gen.lines.zeroBased() - current_line;
|
||||
try writer.writeByteNTimes(';', @intCast(inc));
|
||||
current_line = gen.lines.zeroBased();
|
||||
last_col = 0;
|
||||
} else if (i != 0) {
|
||||
try writer.writeByte(',');
|
||||
}
|
||||
try VLQ.encode(gen.columns.zeroBased() - last_col).writeTo(writer);
|
||||
last_col = gen.columns.zeroBased();
|
||||
try VLQ.encode(source_index - last_src).writeTo(writer);
|
||||
last_src = source_index;
|
||||
try VLQ.encode(orig.lines.zeroBased() - last_ol).writeTo(writer);
|
||||
last_ol = orig.lines.zeroBased();
|
||||
try VLQ.encode(orig.columns.zeroBased() - last_oc).writeTo(writer);
|
||||
last_oc = orig.columns.zeroBased();
|
||||
switch (map.mappings) {
|
||||
.list => |*list| {
|
||||
var last_col: i32 = 0;
|
||||
var last_src: i32 = 0;
|
||||
var last_ol: i32 = 0;
|
||||
var last_oc: i32 = 0;
|
||||
var current_line: i32 = 0;
|
||||
for (
|
||||
list.generated(),
|
||||
list.original(),
|
||||
list.sourceIndex(),
|
||||
0..,
|
||||
) |gen, orig, source_index, i| {
|
||||
if (current_line != gen.lines.zeroBased()) {
|
||||
assert(gen.lines.zeroBased() > current_line);
|
||||
const inc = gen.lines.zeroBased() - current_line;
|
||||
try writer.writeByteNTimes(';', @intCast(inc));
|
||||
current_line = gen.lines.zeroBased();
|
||||
last_col = 0;
|
||||
} else if (i != 0) {
|
||||
try writer.writeByte(',');
|
||||
}
|
||||
try VLQ.encode(gen.columns.zeroBased() - last_col).writeTo(writer);
|
||||
last_col = gen.columns.zeroBased();
|
||||
try VLQ.encode(source_index - last_src).writeTo(writer);
|
||||
last_src = source_index;
|
||||
try VLQ.encode(orig.lines.zeroBased() - last_ol).writeTo(writer);
|
||||
last_ol = orig.lines.zeroBased();
|
||||
try VLQ.encode(orig.columns.zeroBased() - last_oc).writeTo(writer);
|
||||
last_oc = orig.columns.zeroBased();
|
||||
}
|
||||
},
|
||||
.compact => |compact| {
|
||||
// For compact format, just write the raw VLQ mappings
|
||||
try writer.writeAll(compact.vlq_mappings);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user