use bun_collections::VecExt;
use core::mem::size_of;
use bun_ast::Loc;
use bun_collections::MultiArrayList;
use bun_core::{self, ZigStringSlice};
use bun_core::{declare_scope, err, scoped_log};
use bun_semver::String as SemverString;
use crate::vlq::decode as decode_vlq;
use crate::{LineColumnOffset, Ordinal, ParseFail, ParseResult, ParsedSourceMap};
declare_scope!(SourceMap, visible);
trait MappingColumns {
fn items_generated(&self) -> &[LineColumnOffset];
fn items_original(&self) -> &[LineColumnOffset];
fn items_source_index(&self) -> &[i32];
}
impl MappingColumns for MultiArrayList<MappingWithoutName> {
fn items_generated(&self) -> &[LineColumnOffset] {
self.items_named::<LineColumnOffset>("generated")
}
fn items_original(&self) -> &[LineColumnOffset] {
self.items_named::<LineColumnOffset>("original")
}
fn items_source_index(&self) -> &[i32] {
self.items_named::<i32>("source_index")
}
}
impl MappingColumns for MultiArrayList<Mapping> {
fn items_generated(&self) -> &[LineColumnOffset] {
self.items_named::<LineColumnOffset>("generated")
}
fn items_original(&self) -> &[LineColumnOffset] {
self.items_named::<LineColumnOffset>("original")
}
fn items_source_index(&self) -> &[i32] {
self.items_named::<i32>("source_index")
}
}
trait MappingNameColumn {
fn items_name_index(&self) -> &[i32];
}
impl MappingNameColumn for MultiArrayList<Mapping> {
fn items_name_index(&self) -> &[i32] {
self.items_named::<i32>("name_index")
}
}
#[derive(bun_collections::SoaRowDerive, Clone, Copy)]
pub struct Mapping {
pub generated: LineColumnOffset,
pub original: LineColumnOffset,
pub source_index: i32,
pub name_index: i32, }
impl Default for Mapping {
fn default() -> Self {
Self {
generated: LineColumnOffset::default(),
original: LineColumnOffset::default(),
source_index: 0,
name_index: -1,
}
}
}
#[derive(bun_collections::SoaRowDerive, Clone, Copy, Default)]
pub struct MappingWithoutName {
pub generated: LineColumnOffset,
pub original: LineColumnOffset,
pub source_index: i32,
}
impl MappingWithoutName {
pub(crate) fn to_named(&self) -> Mapping {
Mapping {
generated: self.generated,
original: self.original,
source_index: self.source_index,
name_index: -1,
}
}
}
pub enum ListValue {
WithoutNames(MultiArrayList<MappingWithoutName>),
WithNames(MultiArrayList<Mapping>),
}
impl Default for ListValue {
fn default() -> Self {
ListValue::WithoutNames(MultiArrayList::default())
}
}
macro_rules! both_lists {
($v:expr, |$l:ident| $body:expr) => {
match $v {
ListValue::WithoutNames($l) => $body,
ListValue::WithNames($l) => $body,
}
};
}
impl ListValue {
pub(crate) fn memory_cost(&self) -> usize {
both_lists!(self, |list| list.memory_cost())
}
pub(crate) fn ensure_total_capacity(
&mut self,
count: usize,
) -> Result<(), bun_alloc::AllocError> {
both_lists!(self, |list| list.ensure_total_capacity(count))
}
}
#[derive(Default)]
pub struct List {
pub r#impl: ListValue,
pub names: Box<[SemverString]>,
pub names_buffer: Vec<u8>,
}
impl List {
fn ensure_with_names(&mut self) -> Result<(), bun_alloc::AllocError> {
if matches!(self.r#impl, ListValue::WithNames(_)) {
return Ok(());
}
let ListValue::WithoutNames(without_names) = core::mem::replace(
&mut self.r#impl,
ListValue::WithNames(MultiArrayList::default()),
) else {
unreachable!()
};
let mut with_names: MultiArrayList<Mapping> = MultiArrayList::default();
with_names.ensure_total_capacity(without_names.len())?;
for i in 0..without_names.len() {
with_names.append_assume_capacity(without_names.get(i).to_named());
}
self.r#impl = ListValue::WithNames(with_names);
Ok(())
}
fn find_index_from_generated(
line_column_offsets: &[LineColumnOffset],
line: Ordinal,
column: Ordinal,
) -> Option<usize> {
let mut count = line_column_offsets.len();
let mut index: usize = 0;
while count > 0 {
let step = count / 2;
let i: usize = index + step;
let mapping = line_column_offsets[i];
if mapping.lines.zero_based() < line.zero_based()
|| (mapping.lines.zero_based() == line.zero_based()
&& mapping.columns.zero_based() <= column.zero_based())
{
index = i + 1;
count = count.saturating_sub(step + 1);
} else {
count = step;
}
}
if index > 0 {
if line_column_offsets[index - 1].lines.zero_based() == line.zero_based() {
return Some(index - 1);
}
}
None
}
pub fn find_index(&self, line: Ordinal, column: Ordinal) -> Option<usize> {
both_lists!(&self.r#impl, |list| Self::find_index_from_generated(
list.items_generated(),
line,
column,
))
}
pub fn sort(&mut self) {
both_lists!(&mut self.r#impl, |list| {
let generated: *const LineColumnOffset =
list.items_raw_named::<LineColumnOffset>("generated");
let len = list.len();
list.sort(&SortContext { generated, len });
})
}
pub fn append(&mut self, mapping: &Mapping) -> Result<(), bun_alloc::AllocError> {
match &mut self.r#impl {
ListValue::WithoutNames(list) => {
list.append(MappingWithoutName {
generated: mapping.generated,
original: mapping.original,
source_index: mapping.source_index,
})?;
}
ListValue::WithNames(list) => {
list.append(*mapping)?;
}
}
Ok(())
}
pub fn find(&self, line: Ordinal, column: Ordinal) -> Option<Mapping> {
match &self.r#impl {
ListValue::WithoutNames(list) => {
if let Some(i) =
Self::find_index_from_generated(list.items_generated(), line, column)
{
return Some(list.get(i).to_named());
}
}
ListValue::WithNames(list) => {
if let Some(i) =
Self::find_index_from_generated(list.items_generated(), line, column)
{
return Some(*list.get(i));
}
}
}
None
}
pub fn generated(&self) -> &[LineColumnOffset] {
both_lists!(&self.r#impl, |list| list.items_generated())
}
pub fn original(&self) -> &[LineColumnOffset] {
both_lists!(&self.r#impl, |list| list.items_original())
}
pub fn source_index(&self) -> &[i32] {
both_lists!(&self.r#impl, |list| list.items_source_index())
}
pub fn name_index(&self) -> &[i32] {
match &self.r#impl {
ListValue::WithoutNames(_list) => &[],
ListValue::WithNames(list) => list.items_name_index(),
}
}
pub fn get_name(&self, index: i32) -> Option<&[u8]> {
if index < 0 {
return None;
}
let i = usize::try_from(index).expect("int cast");
if i >= self.names.len() {
return None;
}
if matches!(self.r#impl, ListValue::WithNames(_)) {
let str: &SemverString = &self.names[i];
return Some(str.slice(self.names_buffer.slice()));
}
None
}
pub fn memory_cost(&self) -> usize {
self.r#impl.memory_cost()
+ self.names_buffer.memory_cost()
+ (self.names.len() * size_of::<SemverString>())
}
pub fn ensure_total_capacity(&mut self, count: usize) -> Result<(), bun_alloc::AllocError> {
self.r#impl.ensure_total_capacity(count)
}
}
struct SortContext {
generated: *const LineColumnOffset,
len: usize,
}
impl bun_collections::multi_array_list::SortContext for SortContext {
fn less_than(&self, a_index: usize, b_index: usize) -> bool {
debug_assert!(a_index < self.len && b_index < self.len);
let (a, b) = unsafe { (*self.generated.add(a_index), *self.generated.add(b_index)) };
if a.lines.zero_based() != b.lines.zero_based() {
return a.lines.zero_based() < b.lines.zero_based();
}
if a.columns.zero_based() != b.columns.zero_based() {
return a.columns.zero_based() < b.columns.zero_based();
}
a_index < b_index
}
}
pub struct Lookup {
pub mapping: Mapping,
pub source_map: Option<std::sync::Arc<ParsedSourceMap>>,
pub prefetched_source_code: Option<Box<[u8]>>,
pub name: Option<Box<[u8]>>,
}
impl Lookup {
pub fn display_source_url_if_needed(&self, base_filename: &[u8]) -> Option<bun_core::String> {
let source_map = self.source_map.as_deref()?;
if source_map.external_source_names.len() == 0 {
return None;
}
let source_idx = usize::try_from(self.mapping.source_index).ok()?;
if source_idx >= source_map.external_source_names.len() {
return None;
}
let name: &[u8] = &source_map.external_source_names[source_idx];
if source_map.is_standalone_module_graph {
return Some(bun_core::String::clone_utf8(name));
}
if bun_paths::is_absolute(base_filename) {
let dir = bun_paths::resolve_path::dirname::<bun_paths::platform::Auto>(base_filename);
return Some(bun_core::String::clone_utf8(
bun_paths::resolve_path::join_abs::<bun_paths::platform::Auto>(dir, name),
));
}
Some(bun_core::String::borrow_utf8(name))
}
pub fn get_source_code(self, base_filename: &[u8]) -> Option<ZigStringSlice> {
let bytes: Vec<u8> = 'bytes: {
if let Some(code) = self.prefetched_source_code {
break 'bytes code.into_vec();
}
let source_map = self.source_map.as_deref()?;
debug_assert!(source_map.is_external());
let provider = source_map.underlying_provider.provider()?;
let index = usize::try_from(self.mapping.source_index).ok()?;
if source_map.is_standalone_module_graph {
let serialized = source_map.standalone_module_graph_data();
if index >= source_map.external_source_names.len() {
return None;
}
let code = unsafe { (*serialized).source_file_contents(index) };
return Some(ZigStringSlice::from_utf8_never_free(code?));
}
if let Some(parsed) = provider.get_source_map(
base_filename,
source_map.underlying_provider.load_hint(),
crate::ParseUrlResultHint::SourceOnly(u32::try_from(index).expect("int cast")),
) {
if let Some(contents) = parsed.source_contents {
break 'bytes contents.into_vec();
}
}
if index >= source_map.external_source_names.len() {
return None;
}
let name: &[u8] = &source_map.external_source_names[index];
let mut buf = bun_paths::PathBuffer::uninit();
let dir = bun_paths::resolve_path::dirname::<bun_paths::platform::Auto>(base_filename);
let normalized = bun_paths::resolve_path::join_abs_string_buf_z::<
bun_paths::platform::Loose,
>(dir, &mut buf, &[name]);
match bun_sys::File::read_from(bun_sys::Fd::cwd(), normalized) {
Ok(r) => break 'bytes r,
Err(_) => return None,
}
};
Some(ZigStringSlice::init_owned(bytes))
}
}
impl Mapping {
#[inline]
pub fn generated_line(&self) -> i32 {
self.generated.lines.zero_based()
}
#[inline]
pub fn generated_column(&self) -> i32 {
self.generated.columns.zero_based()
}
#[inline]
pub fn source_index(&self) -> i32 {
self.source_index
}
#[inline]
pub fn original_line(&self) -> i32 {
self.original.lines.zero_based()
}
#[inline]
pub fn original_column(&self) -> i32 {
self.original.columns.zero_based()
}
#[inline]
pub fn name_index(&self) -> i32 {
self.name_index
}
}
#[derive(Default, Clone, Copy)]
pub struct ParseOptions {
pub allow_names: bool,
pub sort: bool,
}
const HALF_USIZE: usize = size_of::<usize>() / 2;
const SEMICOLON_RUN: [u8; HALF_USIZE] = [b';'; HALF_USIZE];
pub fn parse(
bytes: &[u8],
estimated_mapping_count: Option<usize>,
sources_count: i32,
input_line_count: usize,
options: ParseOptions,
) -> ParseResult {
scoped_log!(SourceMap, "parse mappings ({} bytes)", bytes.len());
let mut mapping = List::default();
if let Some(count) = estimated_mapping_count {
if mapping.ensure_total_capacity(count).is_err() {
return Err(ParseFail {
err: err!("OutOfMemory"),
loc: Loc::default(),
});
}
}
let mut generated = LineColumnOffset {
lines: Ordinal::START,
columns: Ordinal::START,
};
let mut original = LineColumnOffset {
lines: Ordinal::START,
columns: Ordinal::START,
};
let mut name_index: i32 = 0;
let mut source_index: i32 = 0;
let mut needs_sort = false;
let mut remain = bytes;
let mut has_names = false;
while remain.len() > 0 {
if remain[0] == b';' {
generated.columns = Ordinal::START;
while remain.starts_with(&SEMICOLON_RUN) {
generated.lines = generated.lines.add_scalar(HALF_USIZE as i32);
remain = &remain[HALF_USIZE..];
}
while remain.len() > 0 && remain[0] == b';' {
generated.lines = generated.lines.add_scalar(1);
remain = &remain[1..];
}
if remain.len() == 0 {
break;
}
}
let generated_column_delta = decode_vlq(remain, 0);
if generated_column_delta.start == 0 {
return Err(ParseFail {
err: err!("MissingGeneratedColumnValue"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
needs_sort = needs_sort || generated_column_delta.value < 0;
generated.columns = generated.columns.add_scalar(generated_column_delta.value);
if generated.columns.zero_based() < 0 {
return Err(ParseFail {
err: err!("InvalidGeneratedColumnValue"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
remain = &remain[generated_column_delta.start..];
if remain.len() == 0 {
break;
}
match remain[0] {
b',' => {
remain = &remain[1..];
continue;
}
b';' => {
continue;
}
_ => {}
}
let source_index_delta = decode_vlq(remain, 0);
if source_index_delta.start == 0 {
return Err(ParseFail {
err: err!("InvalidSourceIndexDelta"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
source_index += source_index_delta.value;
if source_index < 0 || source_index >= sources_count {
return Err(ParseFail {
err: err!("InvalidSourceIndexValue"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
remain = &remain[source_index_delta.start..];
let original_line_delta = decode_vlq(remain, 0);
if original_line_delta.start == 0 {
return Err(ParseFail {
err: err!("MissingOriginalLine"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
original.lines = original.lines.add_scalar(original_line_delta.value);
if original.lines.zero_based() < 0 {
return Err(ParseFail {
err: err!("InvalidOriginalLineValue"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
remain = &remain[original_line_delta.start..];
let original_column_delta = decode_vlq(remain, 0);
if original_column_delta.start == 0 {
return Err(ParseFail {
err: err!("MissingOriginalColumnValue"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
original.columns = original.columns.add_scalar(original_column_delta.value);
if original.columns.zero_based() < 0 {
return Err(ParseFail {
err: err!("InvalidOriginalColumnValue"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len()).unwrap_or(i32::MAX),
},
});
}
remain = &remain[original_column_delta.start..];
if remain.len() > 0 {
match remain[0] {
b',' => {
remain = &remain[1..];
}
b';' => {}
_ => {
let name_index_delta = decode_vlq(remain, 0);
if name_index_delta.start == 0 {
return Err(ParseFail {
err: err!("InvalidNameIndexDelta"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len())
.unwrap_or(i32::MAX),
},
});
}
remain = &remain[name_index_delta.start..];
if options.allow_names {
name_index += name_index_delta.value;
if !has_names {
if mapping.ensure_with_names().is_err() {
return Err(ParseFail {
err: err!("OutOfMemory"),
loc: Loc {
start: i32::try_from(bytes.len() - remain.len())
.unwrap_or(i32::MAX),
},
});
}
}
has_names = true;
}
if remain.len() > 0 {
match remain[0] {
b',' => {
remain = &remain[1..];
}
b';' => {}
_ => {}
}
}
}
}
}
mapping
.append(&Mapping {
generated,
original,
source_index,
name_index,
})
.expect("OOM");
}
if needs_sort && options.sort {
mapping.sort();
}
let mut psm = ParsedSourceMap::default();
psm.mappings = mapping;
psm.input_line_count = input_line_count;
Ok(psm)
}