feat: add oxc sourcemap crate (#2825)

The sourcemap implement port from
[rust-sourcemap](https://github.com/getsentry/rust-sourcemap), but has
some different with it.

- Encode sourcemap at parallel, including quote `sourceContent` and
encode token to `vlq` mappings.
- Avoid `Sourcemap` some methods overhead, like `SourceMap::tokens()`
caused extra overhead at common cases. Here using `SourceViewToken` to
instead of it.
This commit is contained in:
underfin 2024-03-28 19:36:38 +08:00 committed by GitHub
parent 2c6bfa3dde
commit b199cb89a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1038 additions and 155 deletions

View file

@ -35,7 +35,7 @@ jobs:
matrix:
# Run each benchmark in own job.
# Linter benchmark is by far the slowest, so split each fixture into own job.
component: [lexer, parser, transformer, semantic, minifier, codegen_sourcemap]
component: [lexer, parser, transformer, semantic, minifier, codegen_sourcemap, sourcemap]
include:
- component: linter
fixture: 0

131
Cargo.lock generated
View file

@ -129,18 +129,6 @@ dependencies = [
"serde",
]
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -465,22 +453,6 @@ dependencies = [
"parking_lot_core",
]
[[package]]
name = "data-encoding"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "debugid"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
dependencies = [
"serde",
"uuid",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -607,12 +579,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.30"
@ -834,12 +800,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "if_chain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]]
name = "ignore"
version = "0.4.22"
@ -1173,7 +1133,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"semver 1.0.22",
"semver",
"syn 2.0.55",
]
@ -1194,7 +1154,7 @@ checksum = "ad444c78696e8b4de2de8a45468d0cc76630e2ed235c84e102c197b89d295a0b"
dependencies = [
"indexmap",
"rustc-hash",
"semver 1.0.22",
"semver",
"serde",
"serde_json",
"thiserror",
@ -1346,6 +1306,7 @@ dependencies = [
"oxc_minifier",
"oxc_parser",
"oxc_semantic",
"oxc_sourcemap",
"oxc_span",
"oxc_syntax",
"oxc_transformer",
@ -1389,6 +1350,7 @@ dependencies = [
"oxc_parser",
"oxc_prettier",
"oxc_semantic",
"oxc_sourcemap",
"oxc_span",
"oxc_tasks_common",
"oxc_transformer",
@ -1426,10 +1388,9 @@ dependencies = [
"oxc_allocator",
"oxc_ast",
"oxc_parser",
"oxc_sourcemap",
"oxc_span",
"oxc_syntax",
"rustc-hash",
"sourcemap",
]
[[package]]
@ -1449,6 +1410,7 @@ dependencies = [
"oxc_parser",
"oxc_prettier",
"oxc_semantic",
"oxc_sourcemap",
"oxc_span",
"oxc_tasks_common",
"phf",
@ -1723,6 +1685,17 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "oxc_sourcemap"
version = "0.10.0"
dependencies = [
"base64-simd",
"rayon",
"rustc-hash",
"serde",
"serde_json",
]
[[package]]
name = "oxc_span"
version = "0.10.0"
@ -2026,12 +1999,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@ -2183,15 +2150,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustix"
version = "0.38.32"
@ -2269,15 +2227,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.22"
@ -2287,12 +2236,6 @@ dependencies = [
"serde",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "seq-macro"
version = "0.3.5"
@ -2458,25 +2401,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "sourcemap"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf0b8c0d9d32f81aa0ab2b68ab634d9bbce287423606656fddb456ac8601aec3"
dependencies = [
"base64-simd",
"bitvec",
"data-encoding",
"debugid",
"if_chain",
"rustc-hash",
"rustc_version",
"serde",
"serde_json",
"unicode-id-start",
"url",
]
[[package]]
name = "spin"
version = "0.9.8"
@ -2532,12 +2456,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.10.1"
@ -2944,12 +2862,6 @@ dependencies = [
"serde",
]
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
[[package]]
name = "valuable"
version = "0.1.0"
@ -3223,15 +3135,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "yansi"
version = "1.0.1"

View file

@ -78,6 +78,7 @@ oxc_semantic = { version = "0.10.0", path = "crates/oxc_semantic" }
oxc_span = { version = "0.10.0", path = "crates/oxc_span" }
oxc_syntax = { version = "0.10.0", path = "crates/oxc_syntax" }
oxc_transformer = { version = "0.10.0", path = "crates/oxc_transformer" }
oxc_sourcemap = { version = "0.10.0", path = "crates/oxc_sourcemap" }
# publish = false
oxc_macros = { path = "crates/oxc_macros" }
@ -160,7 +161,6 @@ petgraph = "0.6.4"
rust-lapper = "1.1.0"
serde_yaml = "0.9.34"
similar = "2.4.0"
sourcemap = "8.0.0"
textwrap = "0.16.0"
unicode-width = "0.1.11"

View file

@ -31,6 +31,7 @@ oxc_semantic = { workspace = true, optional = true }
oxc_transformer = { workspace = true, optional = true }
oxc_minifier = { workspace = true, optional = true }
oxc_codegen = { workspace = true, optional = true }
oxc_sourcemap = { workspace = true, optional = true }
[features]
serialize = ["oxc_ast/serialize", "oxc_span/serialize", "oxc_syntax/serialize", "oxc_semantic?/serialize"]
@ -38,3 +39,4 @@ semantic = ["oxc_semantic"]
transformer = ["oxc_transformer"]
minifier = ["oxc_minifier"]
codegen = ["oxc_codegen"]
sourcemap = ["oxc_sourcemap"]

View file

@ -60,3 +60,9 @@ pub mod codegen {
#[doc(inline)]
pub use oxc_codegen::*;
}
#[cfg(feature = "sourcemap")]
pub mod sourcemap {
#[doc(inline)]
pub use oxc_sourcemap::*;
}

View file

@ -23,9 +23,8 @@ oxc_ast = { workspace = true }
oxc_span = { workspace = true }
oxc_allocator = { workspace = true }
oxc_syntax = { workspace = true }
sourcemap = { workspace = true }
oxc_sourcemap = { workspace = true }
bitflags = { workspace = true }
rustc-hash = { workspace = true }
[dev-dependencies]
oxc_parser = { workspace = true }

View file

@ -33,9 +33,7 @@ fn main() -> std::io::Result<()> {
.build(&ret.program);
if let Some(source_map) = source_map {
let mut buff = vec![];
source_map.to_writer(&mut buff).unwrap();
let result = String::from_utf8(buff).unwrap();
let result = source_map.to_json_string();
let hash = BASE64_STANDARD.encode(format!(
"{}\0{}{}\0{}",
source_text.len(),

View file

@ -13,7 +13,6 @@ mod gen;
mod gen_ts;
mod operator;
mod sourcemap_builder;
mod sourcemap_visualizer;
use std::str::from_utf8_unchecked;
#[allow(clippy::wildcard_imports)]
@ -31,7 +30,6 @@ pub use crate::{
context::Context,
gen::{Gen, GenExpr},
operator::Operator,
sourcemap_visualizer::SourcemapVisualizer,
};
// use crate::mangler::Mangler;
@ -46,7 +44,7 @@ pub struct CodegenOptions {
pub struct CodegenReturn {
pub source_text: String,
pub source_map: Option<sourcemap::SourceMap>,
pub source_map: Option<oxc_sourcemap::SourceMap>,
}
pub struct Codegen<const MINIFY: bool> {

View file

@ -18,6 +18,7 @@ pub struct LineOffsetTable {
byte_offset_to_start_of_line: usize,
}
#[derive(Default)]
#[allow(clippy::struct_field_names)]
pub struct SourcemapBuilder {
enable_sourcemap: bool,
@ -26,36 +27,19 @@ pub struct SourcemapBuilder {
last_position: Option<u32>,
last_search_line: usize,
line_offset_tables: Vec<LineOffsetTable>,
sourcemap_builder: sourcemap::SourceMapBuilder,
sourcemap_builder: oxc_sourcemap::SourceMapBuilder,
generated_line: u32,
generated_column: u32,
}
impl Default for SourcemapBuilder {
fn default() -> Self {
Self {
enable_sourcemap: false,
source_id: 0,
last_generated_update: 0,
last_position: None,
last_search_line: 0,
line_offset_tables: vec![],
sourcemap_builder: sourcemap::SourceMapBuilder::new(None),
generated_line: 0,
generated_column: 0,
}
}
}
impl SourcemapBuilder {
pub fn with_name_and_source(&mut self, name: &str, source: &str) {
self.enable_sourcemap = true;
self.line_offset_tables = Self::generate_line_offset_tables(source);
self.source_id = self.sourcemap_builder.add_source(name);
self.sourcemap_builder.set_source_contents(self.source_id, Some(source));
self.source_id = self.sourcemap_builder.set_source_and_content(name, source);
}
pub fn into_sourcemap(self) -> Option<sourcemap::SourceMap> {
pub fn into_sourcemap(self) -> Option<oxc_sourcemap::SourceMap> {
self.enable_sourcemap.then(|| self.sourcemap_builder.into_sourcemap())
}
@ -67,14 +51,13 @@ impl SourcemapBuilder {
let (original_line, original_column) = self.search_original_line_and_column(position);
self.update_generated_line_and_column(output);
let name_id = name.map(|s| self.sourcemap_builder.add_name(s));
self.sourcemap_builder.add_raw(
self.sourcemap_builder.add_token(
self.generated_line,
self.generated_column,
original_line,
original_column,
Some(self.source_id),
name_id,
false,
);
self.last_position = Some(position);
}

View file

@ -0,0 +1,26 @@
[package]
name = "oxc_sourcemap"
version = "0.10.0"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
categories.workspace = true
include = ["/src"]
[lints]
workspace = true
[lib]
doctest = false
[dependencies]
rustc-hash = { workspace = true }
rayon = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
base64-simd = "0.7"

View file

@ -0,0 +1,4 @@
The sourcemap implement port from [rust-sourcemap](https://github.com/getsentry/rust-sourcemap), but has some different with it.
- Encode sourcemap at parallel, including quote `sourceContent` and encode token to `vlq` mappings.
- Avoid `Sourcemap` some methods overhead, like `SourceMap::tokens()`.

View file

@ -0,0 +1,134 @@
use crate::{token::TokenChunk, SourceMap, Token};
use std::sync::Arc;
/// The `ConcatSourceMapBuilder` is a helper to concat sourcemaps.
#[derive(Debug, Default)]
pub struct ConcatSourceMapBuilder {
pub(crate) names: Vec<Arc<str>>,
pub(crate) sources: Vec<Arc<str>>,
pub(crate) source_contents: Vec<Arc<str>>,
pub(crate) tokens: Vec<Token>,
/// The `token_chunks` is used for encode tokens to vlq mappings at parallel.
pub(crate) token_chunks: Vec<TokenChunk>,
pub(crate) token_chunk_prev_name_id: u32,
}
#[allow(clippy::cast_possible_truncation)]
impl ConcatSourceMapBuilder {
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;
// Add `token_chunks`, See `TokenChunk`.
if let Some(last_token) = self.tokens.last() {
self.token_chunks.push(TokenChunk::new(
self.tokens.len() as u32,
self.tokens.len() as u32 + sourcemap.tokens.len() as u32,
last_token.get_dst_line(),
last_token.get_dst_col(),
last_token.get_src_line(),
last_token.get_src_col(),
self.token_chunk_prev_name_id,
source_offset - 1,
));
} else {
self.token_chunks.push(TokenChunk::new(
0,
sourcemap.tokens.len() as u32,
0,
0,
0,
0,
0,
0,
));
}
// Extend `sources` and `source_contents`.
self.sources.reserve(sourcemap.sources.len());
for (index, source) in sourcemap.get_sources().enumerate() {
let source_content = sourcemap.get_source_content(index as u32).unwrap_or_default();
self.sources.push(source.into());
self.source_contents.push(source_content.into());
}
// Extend `names`.
self.names.reserve(sourcemap.names.len());
self.names.extend(sourcemap.get_names().map(Into::into));
// Extend `tokens`.
self.tokens.reserve(sourcemap.tokens.len());
let tokens = sourcemap.get_tokens().map(|token| {
Token::new(
token.get_dst_line() + line_offset,
token.get_dst_col(),
token.get_src_line(),
token.get_src_col(),
token.get_source_id().map(|x| x + source_offset),
token.get_name_id().map(|x| {
self.token_chunk_prev_name_id = x + name_offset;
self.token_chunk_prev_name_id
}),
)
});
self.tokens.extend(tokens);
}
pub fn into_sourcemap(self) -> SourceMap {
SourceMap::new(
None,
self.names,
self.sources,
Some(self.source_contents),
self.tokens,
Some(self.token_chunks),
)
}
}
#[test]
fn test_concat_sourcemap_builder() {
let sm1 = SourceMap::new(
None,
vec!["foo".into(), "foo2".into()],
vec!["foo.js".into()],
None,
vec![Token::new(1, 1, 1, 1, Some(0), Some(0))],
None,
);
let sm2 = SourceMap::new(
None,
vec!["bar".into()],
vec!["bar.js".into()],
None,
vec![Token::new(1, 1, 1, 1, Some(0), Some(0))],
None,
);
let mut builder = ConcatSourceMapBuilder::default();
builder.add_sourcemap(&sm1, 0);
builder.add_sourcemap(&sm2, 2);
let sm = SourceMap::new(
None,
vec!["foo".into(), "foo2".into(), "bar".into()],
vec!["foo.js".into(), "bar.js".into()],
None,
vec![Token::new(1, 1, 1, 1, Some(0), Some(0)), Token::new(3, 1, 1, 1, Some(1), Some(2))],
None,
);
let concat_sm = builder.into_sourcemap();
assert_eq!(concat_sm.tokens, sm.tokens);
assert_eq!(concat_sm.sources, sm.sources);
assert_eq!(concat_sm.names, sm.names);
assert_eq!(
concat_sm.token_chunks,
Some(vec![
TokenChunk::new(0, 1, 0, 0, 0, 0, 0, 0,),
TokenChunk::new(1, 2, 1, 1, 1, 1, 0, 0,)
])
);
assert_eq!(sm.to_json_string(), sm.to_json_string());
}

View file

@ -0,0 +1,143 @@
/// Port from https://github.com/getsentry/rust-sourcemap/blob/master/src/decoder.rs
/// It is a helper for decode vlq soucemap string to `SourceMap`.
use crate::error::{Error, Result};
use crate::{SourceMap, Token};
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct JSONSourceMap {
file: Option<String>,
mappings: String,
sources: Vec<String>,
sources_content: Option<Vec<String>>,
names: Vec<String>,
}
pub fn decode(value: &str) -> Result<SourceMap> {
let json: JSONSourceMap = serde_json::from_str(value)?;
let file = json.file.map(Into::into);
let names = json.names.into_iter().map(Into::into).collect::<Vec<_>>();
let sources = json.sources.into_iter().map(Into::into).collect::<Vec<_>>();
let source_contents =
json.sources_content.map(|v| v.into_iter().map(Into::into).collect::<Vec<_>>());
let tokens = decode_mapping(&json.mappings, names.len(), sources.len())?;
Ok(SourceMap::new(file, names, sources, source_contents, tokens, None))
}
#[allow(clippy::cast_possible_truncation)]
fn decode_mapping(mapping: &str, names_len: usize, sources_len: usize) -> Result<Vec<Token>> {
let mut tokens = vec![];
let mut dst_col;
let mut src_id = 0;
let mut src_line = 0;
let mut src_col = 0;
let mut name_id = 0;
let mut nums = Vec::with_capacity(6);
for (dst_line, line) in mapping.split(';').enumerate() {
if line.is_empty() {
continue;
}
dst_col = 0;
for segment in line.split(',') {
if segment.is_empty() {
continue;
}
nums.clear();
parse_vlq_segment_into(segment, &mut nums)?;
dst_col = (i64::from(dst_col) + nums[0]) as u32;
let mut src = !0;
let mut name = !0;
if nums.len() > 1 {
if nums.len() != 4 && nums.len() != 5 {
return Err(Error::BadSegmentSize(nums.len() as u32));
}
src_id = (i64::from(src_id) + nums[1]) as u32;
if src_id >= sources_len as u32 {
return Err(Error::BadSourceReference(src_id));
}
src = src_id;
src_line = (i64::from(src_line) + nums[2]) as u32;
src_col = (i64::from(src_col) + nums[3]) as u32;
if nums.len() > 4 {
name_id = (i64::from(name_id) + nums[4]) as u32;
if name_id >= names_len as u32 {
return Err(Error::BadNameReference(name_id));
}
name = name_id;
}
}
tokens.push(Token::new(
dst_line as u32,
dst_col,
src_line,
src_col,
if src == !0 { None } else { Some(src) },
if name == !0 { None } else { Some(name) },
));
}
}
Ok(tokens)
}
#[rustfmt::skip]
const B64: [i8; 256] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 - 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ];
fn parse_vlq_segment_into(segment: &str, rv: &mut Vec<i64>) -> Result<()> {
let mut cur = 0;
let mut shift = 0;
for c in segment.bytes() {
let enc = i64::from(B64[c as usize]);
let val = enc & 0b11111;
let cont = enc >> 5;
cur += val.checked_shl(shift).ok_or(Error::VlqOverflow)?;
shift += 5;
if cont == 0 {
let sign = cur & 1;
cur >>= 1;
if sign != 0 {
cur = -cur;
}
rv.push(cur);
cur = 0;
shift = 0;
}
}
if cur != 0 || shift != 0 {
Err(Error::VlqLeftover)
} else if rv.is_empty() {
Err(Error::VlqNoValues)
} else {
Ok(())
}
}
#[test]
fn test_decode_sourcemap() {
let input = r#"{
"version": 3,
"sources": ["coolstuff.js"],
"sourceRoot": "x",
"names": ["x","alert"],
"mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM"
}"#;
let sm = SourceMap::from_json_string(input).unwrap();
let mut iter = sm.get_source_view_tokens().filter(|token| token.get_name_id().is_some());
assert_eq!(iter.next().unwrap().to_tuple(), (Some("coolstuff.js"), 0, 4, Some("x")));
assert_eq!(iter.next().unwrap().to_tuple(), (Some("coolstuff.js"), 1, 4, Some("x")));
assert_eq!(iter.next().unwrap().to_tuple(), (Some("coolstuff.js"), 2, 2, Some("alert")));
assert!(iter.next().is_none());
}

View file

@ -0,0 +1,138 @@
/// Port from https://github.com/getsentry/rust-sourcemap/blob/master/src/encoder.rs
/// It is a helper for encode `SourceMap` to vlq sourcemap string, but here some different.
/// - Quote `source_content` at parallel.
/// - If you using `ConcatSourceMapBuilder`, serialize `tokens` to vlq `mappings` at parallel.
use crate::{token::TokenChunk, SourceMap, Token};
use rayon::prelude::*;
pub fn encode(sourcemap: &SourceMap) -> String {
let mut buf = String::new();
buf.push_str("{\"version\":3,");
if let Some(file) = sourcemap.get_file() {
buf.push_str("\"file\":\"");
buf.push_str(file);
buf.push_str("\",");
}
buf.push_str("\"names\":[");
buf.push_str(&sourcemap.names.iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","));
buf.push_str("],\"sources\":[");
buf.push_str(&sourcemap.sources.iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","));
// Quote `source_content` at parallel.
if let Some(source_contents) = &sourcemap.source_contents {
buf.push_str("],\"sourcesContent\":[");
buf.push_str(
&source_contents.par_iter().map(|x| format!("{x:?}")).collect::<Vec<_>>().join(","),
);
}
buf.push_str("],\"mappings\":\"");
buf.push_str(&serialize_sourcemap_mappings(sourcemap));
buf.push_str("\"}");
buf
}
#[allow(clippy::cast_possible_truncation)]
fn serialize_sourcemap_mappings(sm: &SourceMap) -> String {
sm.token_chunks.as_ref().map_or_else(
|| {
serialize_mappings(
&sm.tokens,
&TokenChunk::new(0, sm.tokens.len() as u32, 0, 0, 0, 0, 0, 0),
)
},
|token_chunks| {
// Serialize `tokens` to vlq `mappings` at parallel.
token_chunks
.par_iter()
.map(|token_chunk| serialize_mappings(&sm.tokens, token_chunk))
.collect::<String>()
},
)
}
fn serialize_mappings(tokens: &[Token], token_chunk: &TokenChunk) -> String {
let mut rv = String::new();
let TokenChunk {
start,
end,
mut prev_dst_line,
mut prev_dst_col,
mut prev_src_line,
mut prev_src_col,
mut prev_name_id,
mut prev_source_id,
} = *token_chunk;
for (idx, token) in tokens[start as usize..end as usize].iter().enumerate() {
if token.get_dst_line() != prev_dst_line {
prev_dst_col = 0;
while token.get_dst_line() != prev_dst_line {
rv.push(';');
prev_dst_line += 1;
}
} else if idx > 0 {
if Some(token) == tokens.get(idx - 1) {
continue;
}
rv.push(',');
}
encode_vlq_diff(&mut rv, token.get_dst_col(), prev_dst_col);
prev_dst_col = token.get_dst_col();
if let Some(source_id) = token.get_source_id() {
encode_vlq_diff(&mut rv, source_id, prev_source_id);
prev_source_id = source_id;
encode_vlq_diff(&mut rv, token.get_src_line(), prev_src_line);
prev_src_line = token.get_src_line();
encode_vlq_diff(&mut rv, token.get_src_col(), prev_src_col);
prev_src_col = token.get_src_col();
if let Some(name_id) = token.get_name_id() {
encode_vlq_diff(&mut rv, name_id, prev_name_id);
prev_name_id = name_id;
}
}
}
rv
}
#[inline]
fn encode_vlq_diff(out: &mut String, a: u32, b: u32) {
encode_vlq(out, i64::from(a) - i64::from(b));
}
const B64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn encode_vlq(out: &mut String, num: i64) {
let mut num = if num < 0 { ((-num) << 1) + 1 } else { num << 1 };
loop {
let mut digit = num & 0b11111;
num >>= 5;
if num > 0 {
digit |= 1 << 5;
}
out.push(B64_CHARS[digit as usize] as char);
if num == 0 {
break;
}
}
}
#[test]
fn test_encode() {
let input = r#"{
"version": 3,
"sources": ["coolstuff.js"],
"names": ["x","alert"],
"mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM"
}"#;
let sm = SourceMap::from_json_string(input).unwrap();
let sm2 = SourceMap::from_json_string(&sm.to_json_string()).unwrap();
for (tok1, tok2) in sm.get_tokens().zip(sm2.get_tokens()) {
assert_eq!(tok1, tok2);
}
}

View file

@ -0,0 +1,26 @@
#[derive(Debug)]
pub enum Error {
/// a VLQ string was malformed and data was left over
VlqLeftover,
/// a VLQ string was empty and no values could be decoded.
VlqNoValues,
/// The input encoded a number that didn't fit into i64.
VlqOverflow,
/// `serde_json` parsing failure
BadJson(serde_json::Error),
/// a mapping segment had an unsupported size
BadSegmentSize(u32),
/// a reference to a non existing source was encountered
BadSourceReference(u32),
/// a reference to a non existing name was encountered
BadNameReference(u32),
}
/// The result of decoding.
pub type Result<T> = std::result::Result<T, Error>;
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Error {
Error::BadJson(err)
}
}

View file

@ -0,0 +1,16 @@
mod concat_sourcemap_builder;
#[allow(clippy::cast_sign_loss)]
mod decode;
mod encode;
mod error;
mod sourcemap;
mod sourcemap_builder;
mod sourcemap_visualizer;
mod token;
pub use concat_sourcemap_builder::ConcatSourceMapBuilder;
pub use error::Error;
pub use sourcemap::SourceMap;
pub use sourcemap_builder::SourceMapBuilder;
pub use sourcemap_visualizer::SourcemapVisualizer;
pub use token::{SourceViewToken, Token};

View file

@ -0,0 +1,215 @@
use crate::{
decode::decode,
encode::encode,
error::Result,
token::{Token, TokenChunk},
SourceViewToken,
};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct SourceMap {
pub(crate) file: Option<Arc<str>>,
pub(crate) names: Vec<Arc<str>>,
pub(crate) sources: Vec<Arc<str>>,
pub(crate) source_contents: Option<Vec<Arc<str>>>,
pub(crate) tokens: Vec<Token>,
pub(crate) token_chunks: Option<Vec<TokenChunk>>,
}
#[allow(clippy::cast_possible_truncation)]
impl SourceMap {
pub fn new(
file: Option<Arc<str>>,
names: Vec<Arc<str>>,
sources: Vec<Arc<str>>,
source_contents: Option<Vec<Arc<str>>>,
tokens: Vec<Token>,
token_chunks: Option<Vec<TokenChunk>>,
) -> Self {
Self { file, names, sources, source_contents, tokens, token_chunks }
}
/// Convert `SourceMap` to vlq sourcemap string.
#[allow(clippy::missing_errors_doc)]
pub fn from_json_string(value: &str) -> Result<Self> {
decode(value)
}
/// Convert the vlq sourcemap string to `SourceMap`.
pub fn to_json_string(&self) -> String {
encode(self)
}
/// Convert `SourceMap` to vlq sourcemap data url.
pub fn to_data_url(&self) -> String {
let base_64_str =
base64_simd::Base64::STANDARD.encode_to_boxed_str(self.to_json_string().as_bytes());
format!("data:application/json;charset=utf-8;base64,{base_64_str}")
}
pub fn get_file(&self) -> Option<&str> {
self.file.as_deref()
}
pub fn set_file(&mut self, file: &str) {
self.file = Some(file.into());
}
pub fn get_names(&self) -> impl Iterator<Item = &str> {
self.names.iter().map(std::convert::AsRef::as_ref)
}
pub fn get_sources(&self) -> impl Iterator<Item = &str> {
self.sources.iter().map(std::convert::AsRef::as_ref)
}
pub fn get_source_contents(&self) -> Option<impl Iterator<Item = &str>> {
self.source_contents.as_ref().map(|v| v.iter().map(std::convert::AsRef::as_ref))
}
pub fn get_token(&self, index: u32) -> Option<&Token> {
self.tokens.get(index as usize)
}
/// Get raw tokens.
pub fn get_tokens(&self) -> impl Iterator<Item = &Token> {
self.tokens.iter()
}
/// Get source view tokens. See [`SourceViewToken`] for more information.
pub fn get_source_view_tokens(&self) -> impl Iterator<Item = SourceViewToken<'_>> {
self.tokens.iter().map(|token| SourceViewToken::new(token, self))
}
pub fn get_name(&self, id: u32) -> Option<&str> {
self.names.get(id as usize).map(std::convert::AsRef::as_ref)
}
pub fn get_source(&self, id: u32) -> Option<&str> {
self.sources.get(id as usize).map(std::convert::AsRef::as_ref)
}
pub fn get_source_content(&self, id: u32) -> Option<&str> {
self.source_contents
.as_ref()
.and_then(|x| x.get(id as usize).map(std::convert::AsRef::as_ref))
}
pub fn get_source_and_content(&self, id: u32) -> Option<(&str, &str)> {
let source = self.get_source(id)?;
let content = self.get_source_content(id)?;
Some((source, content))
}
/// Generate a lookup table, it will be used at `lookup_token` or `lookup_source_view_token`.
pub fn generate_lookup_table(&self) -> Vec<(u32, u32, u32)> {
let mut table = self
.tokens
.iter()
.enumerate()
.map(|(idx, token)| (token.dst_line, token.dst_col, idx as u32))
.collect::<Vec<_>>();
table.sort_unstable();
table
}
/// Lookup a token by line and column, it will used at remapping.
pub fn lookup_token(
&self,
lookup_table: &[(u32, u32, u32)],
line: u32,
col: u32,
) -> Option<&Token> {
let table = greatest_lower_bound(lookup_table, &(line, col), |table| (table.0, table.1))?;
self.get_token(table.2)
}
/// Lookup a token by line and column, it will used at remapping. See `SourceViewToken`.
pub fn lookup_source_view_token(
&self,
lookup_table: &[(u32, u32, u32)],
line: u32,
col: u32,
) -> Option<SourceViewToken<'_>> {
self.lookup_token(lookup_table, line, col).map(|token| SourceViewToken::new(token, self))
}
}
fn greatest_lower_bound<'a, T, K: Ord, F: Fn(&'a T) -> K>(
slice: &'a [T],
key: &K,
map: F,
) -> Option<&'a T> {
let mut idx = match slice.binary_search_by_key(key, &map) {
Ok(index) => index,
Err(index) => {
// If there is no match, then we know for certain that the index is where we should
// insert a new token, and that the token directly before is the greatest lower bound.
return slice.get(index.checked_sub(1)?);
}
};
// If we get an exact match, then we need to continue looking at previous tokens to see if
// they also match. We use a linear search because the number of exact matches is generally
// very small, and almost certainly smaller than the number of tokens before the index.
for i in (0..idx).rev() {
if map(&slice[i]) == *key {
idx = i;
} else {
break;
}
}
slice.get(idx)
}
#[test]
fn test_sourcemap_lookup_token() {
let input = r#"{
"version": 3,
"sources": ["coolstuff.js"],
"sourceRoot": "x",
"names": ["x","alert"],
"mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM"
}"#;
let sm = SourceMap::from_json_string(input).unwrap();
let lookup_table = sm.generate_lookup_table();
assert_eq!(
sm.lookup_source_view_token(&lookup_table, 0, 0).unwrap().to_tuple(),
(Some("coolstuff.js"), 0, 0, None)
);
assert_eq!(
sm.lookup_source_view_token(&lookup_table, 0, 3).unwrap().to_tuple(),
(Some("coolstuff.js"), 0, 4, Some("x"))
);
assert_eq!(
sm.lookup_source_view_token(&lookup_table, 0, 24).unwrap().to_tuple(),
(Some("coolstuff.js"), 2, 8, None)
);
// Lines continue out to infinity
assert_eq!(
sm.lookup_source_view_token(&lookup_table, 0, 1000).unwrap().to_tuple(),
(Some("coolstuff.js"), 2, 8, None)
);
// Token can return prior lines.
assert_eq!(
sm.lookup_source_view_token(&lookup_table, 1000, 0).unwrap().to_tuple(),
(Some("coolstuff.js"), 2, 8, None)
);
}
#[test]
fn test_sourcemap_source_view_token() {
let sm = SourceMap::new(
None,
vec!["foo".into()],
vec!["foo.js".into()],
None,
vec![Token::new(1, 1, 1, 1, Some(0), Some(0))],
None,
);
let mut source_view_tokens = sm.get_source_view_tokens();
assert_eq!(source_view_tokens.next().unwrap().to_tuple(), (Some("foo.js"), 1, 1, Some("foo")));
}

View file

@ -0,0 +1,89 @@
use std::sync::Arc;
use rustc_hash::FxHashMap;
use crate::{token::Token, SourceMap};
/// The `SourceMapBuilder` is a helper to generate sourcemap.
#[derive(Debug, Default)]
pub struct SourceMapBuilder {
pub(crate) names_map: FxHashMap<Arc<str>, u32>,
pub(crate) names: Vec<Arc<str>>,
pub(crate) sources: Vec<Arc<str>>,
pub(crate) sources_map: FxHashMap<Arc<str>, u32>,
pub(crate) source_contents: Vec<Arc<str>>,
pub(crate) tokens: Vec<Token>,
}
#[allow(clippy::cast_possible_truncation)]
impl SourceMapBuilder {
/// Add item to `SourceMap::name`.
pub fn add_name(&mut self, name: &str) -> u32 {
let count = self.names.len() as u32;
let id = *self.names_map.entry(name.into()).or_insert(count);
if id == count {
self.names.push(name.into());
}
id
}
/// Add item to `SourceMap::sources` and `SourceMap::source_contents`.
/// If `source` maybe duplicate, please use it.
pub fn add_source_and_content(&mut self, source: &str, source_content: &str) -> u32 {
let count = self.sources.len() as u32;
let id = *self.sources_map.entry(source.into()).or_insert(count);
if id == count {
self.sources.push(source.into());
self.source_contents.push(source_content.into());
}
id
}
/// Add item to `SourceMap::sources` and `SourceMap::source_contents`.
/// If `source` hasn't duplicateit will avoid extra hash calculation.
pub fn set_source_and_content(&mut self, source: &str, source_content: &str) -> u32 {
let count = self.sources.len() as u32;
self.sources.push(source.into());
self.source_contents.push(source_content.into());
count
}
/// Add item to `SourceMap::tokens`.
pub fn add_token(
&mut self,
dst_line: u32,
dst_col: u32,
src_line: u32,
src_col: u32,
src_id: Option<u32>,
name_id: Option<u32>,
) {
self.tokens.push(Token::new(dst_line, dst_col, src_line, src_col, src_id, name_id));
}
pub fn into_sourcemap(self) -> SourceMap {
SourceMap::new(
None,
self.names,
self.sources,
Some(self.source_contents),
self.tokens,
None,
)
}
}
#[test]
fn test_sourcemap_builder() {
let mut builder = SourceMapBuilder::default();
builder.set_source_and_content("baz.js", "");
builder.add_name("x");
let sm = builder.into_sourcemap();
assert_eq!(sm.get_source(0), Some("baz.js"));
assert_eq!(sm.get_name(0), Some("x"));
let expected =
r#"{"version":3,"names":["x"],"sources":["baz.js"],"sourcesContent":[""],"mappings":""}"#;
assert_eq!(expected, sm.to_json_string());
}

View file

@ -1,6 +1,8 @@
use crate::SourceMap;
use rustc_hash::FxHashMap;
use sourcemap::SourceMap;
/// The `SourcemapVisualizer` is a helper for sourcemap testing.
/// It print the mapping of original content and final content tokens.
pub struct SourcemapVisualizer<'a> {
output: &'a str,
sourcemap: &'a SourceMap,
@ -16,22 +18,26 @@ impl<'a> SourcemapVisualizer<'a> {
let mut source_log_map = FxHashMap::default();
let source_contents_lines_map: FxHashMap<String, Option<Vec<Vec<u16>>>> = self
.sourcemap
.sources()
.sources
.iter()
.enumerate()
.map(|(source_id, source)| {
(
source.to_string(),
self.sourcemap
.get_source_view(source_id as u32)
.map(|source_view| Self::generate_line_utf16_tables(source_view.source())),
.get_source_content(source_id as u32)
.map(Self::generate_line_utf16_tables),
)
})
.collect();
let output_lines = Self::generate_line_utf16_tables(self.output);
let mut s = String::new();
self.sourcemap.tokens().reduce(|pre_token, token| {
if let Some(source) = pre_token.get_source() {
self.sourcemap.tokens.iter().reduce(|pre_token, token| {
if let Some(source) =
pre_token.get_source_id().and_then(|id| self.sourcemap.get_source(id))
{
if let Some(Some(source_contents_lines)) = source_contents_lines_map.get(source) {
// Print source
source_log_map.entry(source).or_insert_with(|| {
@ -129,13 +135,13 @@ mod test {
#[test]
fn should_work() {
let sourcemap = SourceMap::from_slice(r#"{
let sourcemap = SourceMap::from_json_string(r#"{
"version":3,
"sources":["shared.js","index.js"],
"sourcesContent":["const a = 'shared.js'\n\nexport { a }","import { a as a2 } from './shared'\nconst a = 'index.js'\nconsole.log(a, a2)\n"],
"names":["a","a$1"],
"mappings":";;AAAA,MAAMA,IAAI;;;ACCV,MAAMC,MAAI;AACV,QAAQ,IAAIA,KAAGD,EAAG"
}"#.as_bytes()).unwrap();
}"#).unwrap();
let output = "\n// shared.js\nconst a = 'shared.js';\n\n// index.js\nconst a$1 = 'index.js';\nconsole.log(a$1, a);\n";
let visualizer = SourcemapVisualizer::new(output, &sourcemap);
let visualizer_text = visualizer.into_visualizer_text();

View file

@ -0,0 +1,145 @@
use crate::SourceMap;
/// The `Token` is used to generate vlq `mappings`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Token {
pub(crate) dst_line: u32,
pub(crate) dst_col: u32,
pub(crate) src_line: u32,
pub(crate) src_col: u32,
pub(crate) source_id: Option<u32>,
pub(crate) name_id: Option<u32>,
}
impl Token {
pub fn new(
dst_line: u32,
dst_col: u32,
src_line: u32,
src_col: u32,
source_id: Option<u32>,
name_id: Option<u32>,
) -> Self {
Self { dst_line, dst_col, src_line, src_col, source_id, name_id }
}
pub fn get_dst_line(&self) -> u32 {
self.dst_line
}
pub fn get_dst_col(&self) -> u32 {
self.dst_col
}
pub fn get_src_line(&self) -> u32 {
self.src_line
}
pub fn get_src_col(&self) -> u32 {
self.src_col
}
pub fn get_name_id(&self) -> Option<u32> {
self.name_id
}
pub fn get_source_id(&self) -> Option<u32> {
self.source_id
}
}
/// The `TokenChunk` used by encode tokens to vlq mappings at parallel.
/// It is a slice of `SourceMap::tokens`, it is a unit of parallel.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TokenChunk {
pub start: u32,
pub end: u32,
pub prev_dst_line: u32,
pub prev_dst_col: u32,
pub prev_src_line: u32,
pub prev_src_col: u32,
pub prev_name_id: u32,
pub prev_source_id: u32,
}
impl TokenChunk {
#[allow(clippy::too_many_arguments)]
pub fn new(
start: u32,
end: u32,
prev_dst_line: u32,
prev_dst_col: u32,
prev_src_line: u32,
prev_src_col: u32,
prev_name_id: u32,
prev_source_id: u32,
) -> Self {
Self {
start,
end,
prev_dst_line,
prev_dst_col,
prev_src_line,
prev_src_col,
prev_name_id,
prev_source_id,
}
}
}
/// The `SourceViewToken` provider extra `source` and `source_content` value.
#[derive(Debug, Clone, Copy)]
pub struct SourceViewToken<'a> {
pub(crate) token: &'a Token,
pub(crate) sourcemap: &'a SourceMap,
}
impl<'a> SourceViewToken<'a> {
pub fn new(token: &'a Token, sourcemap: &'a SourceMap) -> Self {
Self { token, sourcemap }
}
pub fn get_dst_line(&self) -> u32 {
self.token.dst_line
}
pub fn get_dst_col(&self) -> u32 {
self.token.dst_col
}
pub fn get_src_line(&self) -> u32 {
self.token.src_line
}
pub fn get_src_col(&self) -> u32 {
self.token.src_col
}
pub fn get_name_id(&self) -> Option<u32> {
self.token.name_id
}
pub fn get_source_id(&self) -> Option<u32> {
self.token.source_id
}
pub fn get_name(&self) -> Option<&str> {
self.token.name_id.and_then(|id| self.sourcemap.get_name(id))
}
pub fn get_source(&self) -> Option<&str> {
self.token.source_id.and_then(|id| self.sourcemap.get_source(id))
}
pub fn get_source_content(&self) -> Option<&str> {
self.token.source_id.and_then(|id| self.sourcemap.get_source_content(id))
}
pub fn get_source_and_content(&self) -> Option<(&str, &str)> {
self.token.source_id.and_then(|id| self.sourcemap.get_source_and_content(id))
}
pub fn to_tuple(&self) -> (Option<&str>, u32, u32, Option<&str>) {
(self.get_source(), self.get_src_line(), self.get_src_col(), self.get_name())
}
}

View file

@ -42,6 +42,10 @@ harness = false
name = "codegen_sourcemap"
harness = false
[[bench]]
name = "sourcemap"
harness = false
# Broken
# [[bench]]
# name = "prettier"
@ -71,6 +75,7 @@ oxc_span = { workspace = true, optional = true }
oxc_tasks_common = { workspace = true, optional = true }
oxc_transformer = { workspace = true, optional = true }
oxc_codegen = { workspace = true, optional = true }
oxc_sourcemap = { workspace = true, optional = true }
criterion = { package = "criterion2", version = "0.6.0", default-features = false }
serde = { workspace = true, optional = true }
@ -88,6 +93,7 @@ default = [
"dep:oxc_tasks_common",
"dep:oxc_transformer",
"dep:oxc_codegen",
"dep:oxc_sourcemap",
]
codspeed = ["criterion/codspeed"]
codspeed_napi = ["criterion/codspeed", "dep:serde", "dep:serde_json"]

View file

@ -0,0 +1,44 @@
use oxc_allocator::Allocator;
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn};
use oxc_parser::Parser;
use oxc_sourcemap::ConcatSourceMapBuilder;
use oxc_span::SourceType;
use oxc_tasks_common::TestFiles;
#[allow(clippy::cast_possible_truncation)]
fn bench_sourcemap(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("sourcemap");
for file in TestFiles::minimal().files() {
let id = BenchmarkId::from_parameter(&file.file_name);
let source_type = SourceType::from_path(&file.file_name).unwrap();
group.bench_with_input(id, &file.source_text, |b, source_text| {
let allocator = Allocator::default();
let program = Parser::new(&allocator, source_text, source_type).parse().program;
let codegen_options =
CodegenOptions { enable_source_map: true, ..CodegenOptions::default() };
b.iter_with_large_drop(|| {
let CodegenReturn { source_map, source_text } = Codegen::<false>::new(
file.file_name.as_str(),
source_text,
codegen_options.clone(),
)
.build(&program);
let line = source_text.matches('\n').count() as u32;
if let Some(sourcemap) = source_map {
let mut concat_sourcemap_builder = ConcatSourceMapBuilder::default();
for i in 0..1 {
concat_sourcemap_builder.add_sourcemap(&sourcemap, line * i);
}
concat_sourcemap_builder.into_sourcemap().to_json_string();
}
});
});
}
group.finish();
}
criterion_group!(sourcemap, bench_sourcemap);
criterion_main!(sourcemap);

View file

@ -31,6 +31,7 @@ oxc_minifier = { workspace = true }
oxc_prettier = { workspace = true }
oxc_span = { workspace = true }
oxc_tasks_common = { workspace = true }
oxc_sourcemap = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

View file

@ -5,8 +5,9 @@ use std::{
};
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions, SourcemapVisualizer};
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser;
use oxc_sourcemap::SourcemapVisualizer;
use oxc_span::SourceType;
use oxc_tasks_common::{project_root, TestFiles};