feat(sourcemap): add ConcatSourceMapBuilder::from_sourcemaps (#4639)

Introduce new method `ConcatSourceMapBuilder::from_sourcemaps`.

Where all the sourcemaps being concatenated exist at time that you
create `ConcatSourceMapBuilder`, it's faster to use `from_sourcemaps`,
because it pre-allocates enough space for the data it will hold and so
avoids memory copying.

Before:

```rs
let mut builder = ConcatSourceMapBuilder::default();
builder.add_sourcemap(&sourcemap1, 0);
builder.add_sourcemap(&sourcemap2, 100);
builder.add_sourcemap(&sourcemap3, 100);
let combined = builder.into_sourcemap();
```

After:

```rs
let builder = ConcatSourceMapBuilder::from_sourcemaps(&[
    (&sourcemap1, 0),
    (&sourcemap2, 100),
    (&sourcemap3, 200),
]);
let combined = builder.into_sourcemap();
```
This commit is contained in:
overlookmotel 2024-08-06 07:08:17 +01:00 committed by GitHub
parent 9be29af9d4
commit e42ac3a2a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 9 deletions

View file

@ -16,6 +16,66 @@ pub struct ConcatSourceMapBuilder {
#[allow(clippy::cast_possible_truncation)]
impl ConcatSourceMapBuilder {
/// Create new `ConcatSourceMapBuilder` with pre-allocated capacity.
///
/// Allocating capacity before adding sourcemaps with `add_sourcemap` avoids memory copies
/// and increases performance.
///
/// Alternatively, use `from_sourcemaps`.
pub fn with_capacity(
names_len: usize,
sources_len: usize,
tokens_len: usize,
token_chunks_len: usize,
) -> Self {
Self {
names: Vec::with_capacity(names_len),
sources: Vec::with_capacity(sources_len),
source_contents: Vec::with_capacity(sources_len),
tokens: Vec::with_capacity(tokens_len),
token_chunks: Vec::with_capacity(token_chunks_len),
token_chunk_prev_name_id: 0,
}
}
/// Create new `ConcatSourceMapBuilder` from an array of `SourceMap`s and line offsets.
///
/// This avoids memory copies versus creating builder with `ConcatSourceMapBuilder::default()`
/// and then adding sourcemaps individually with `add_sourcemap`.
///
/// # Example
/// ```
/// let builder = ConcatSourceMapBuilder::from_sourcemaps(&[
/// (&sourcemap1, 0),
/// (&sourcemap2, 100),
/// ]);
/// let combined_sourcemap = builder.into_sourcemap();
/// ```
pub fn from_sourcemaps(sourcemap_and_line_offsets: &[(&SourceMap, u32)]) -> Self {
// Calculate length of `Vec`s required
let mut names_len = 0;
let mut sources_len = 0;
let mut tokens_len = 0;
for (sourcemap, _) in sourcemap_and_line_offsets {
names_len += sourcemap.names.len();
sources_len += sourcemap.sources.len();
tokens_len += sourcemap.tokens.len();
}
let mut builder = Self::with_capacity(
names_len,
sources_len,
tokens_len,
sourcemap_and_line_offsets.len(),
);
for (sourcemap, line_offset) in sourcemap_and_line_offsets.iter().copied() {
builder.add_sourcemap(sourcemap, line_offset);
}
builder
}
pub fn add_sourcemap(&mut self, sourcemap: &SourceMap, line_offset: u32) {
let source_offset = self.sources.len() as u32;
let name_offset = self.names.len() as u32;
@ -92,9 +152,27 @@ impl ConcatSourceMapBuilder {
}
}
#[cfg(feature = "concurrent")]
#[test]
fn test_concat_sourcemap_builder() {
run_test(|sourcemap_and_line_offsets| {
let mut builder = ConcatSourceMapBuilder::default();
for (sourcemap, line_offset) in sourcemap_and_line_offsets.iter().copied() {
builder.add_sourcemap(sourcemap, line_offset);
}
builder
});
}
#[test]
fn test_concat_sourcemap_builder_from_sourcemaps() {
run_test(ConcatSourceMapBuilder::from_sourcemaps);
}
#[cfg(test)]
fn run_test<F>(create_builder: F)
where
F: Fn(&[(&SourceMap, u32)]) -> ConcatSourceMapBuilder,
{
let sm1 = SourceMap::new(
None,
vec!["foo".into(), "foo2".into()],
@ -123,10 +201,7 @@ fn test_concat_sourcemap_builder() {
None,
);
let mut builder = ConcatSourceMapBuilder::default();
builder.add_sourcemap(&sm1, 0);
builder.add_sourcemap(&sm2, 2);
builder.add_sourcemap(&sm3, 2);
let builder = create_builder(&[(&sm1, 0), (&sm2, 2), (&sm3, 2)]);
let sm = SourceMap::new(
None,

View file

@ -27,10 +27,10 @@ fn bench_sourcemap(criterion: &mut Criterion) {
.enable_source_map(file.file_name.as_str(), source_text)
.build(&ret.program);
if let Some(sourcemap) = source_map {
let mut concat_sourcemap_builder = ConcatSourceMapBuilder::default();
for i in 0..2 {
concat_sourcemap_builder.add_sourcemap(&sourcemap, lines * i);
}
let concat_sourcemap_builder = ConcatSourceMapBuilder::from_sourcemaps(&[
(&sourcemap, 0),
(&sourcemap, lines),
]);
concat_sourcemap_builder.into_sourcemap().to_json_string();
}
});