1use std::sync::LazyLock;
19
20use anyhow::Result;
21use bincode::{Decode, Encode};
22use serde::Deserialize;
23use serde_json::value::RawValue;
24use turbo_tasks::{NonLocalValue, trace::TraceRawVcs};
25use turbo_tasks_fs::rope::{Rope, RopeBuilder};
26use turbo_tasks_hash::{DeterministicHash, DeterministicHasher};
27
28#[derive(Clone, Debug, Default, PartialEq, Eq, Encode, Decode, TraceRawVcs, NonLocalValue)]
30pub struct StructuredSourceMap {
31 version: Option<Rope>,
34 file: Option<Rope>,
35 sources: Option<SourcesField>,
36 source_root: Option<Rope>,
37 sources_content: Option<SourcesContentField>,
38 sections: Option<Rope>,
39 names: Option<Rope>,
40 scopes: Option<Rope>,
41 range_mappings: Option<Rope>,
42 mappings: Option<Rope>,
43 ignore_list: Option<Rope>,
44 x_facebook_offsets: Option<Rope>,
45 x_metro_module_paths: Option<Rope>,
46 x_facebook_sources: Option<Rope>,
47 debug_id_old: Option<Rope>,
48 debug_id: Option<Rope>,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TraceRawVcs, NonLocalValue)]
54enum SourcesField {
55 Raw(Rope),
57 Rewritten(Vec<Option<String>>),
60}
61
62#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TraceRawVcs, NonLocalValue)]
64enum SourcesContentField {
65 Raw(Rope),
69 Escaped(Vec<Rope>),
73}
74
75#[derive(Deserialize)]
77struct RawFields {
78 version: Option<Box<RawValue>>,
79 file: Option<Box<RawValue>>,
80 sources: Option<Box<RawValue>>,
81 #[serde(rename = "sourceRoot")]
82 source_root: Option<Box<RawValue>>,
83 #[serde(rename = "sourcesContent")]
84 sources_content: Option<Box<RawValue>>,
85 sections: Option<Box<RawValue>>,
86 names: Option<Box<RawValue>>,
87 scopes: Option<Box<RawValue>>,
88 #[serde(rename = "rangeMappings")]
89 range_mappings: Option<Box<RawValue>>,
90 mappings: Option<Box<RawValue>>,
91 #[serde(rename = "ignoreList")]
92 ignore_list: Option<Box<RawValue>>,
93 x_facebook_offsets: Option<Box<RawValue>>,
94 x_metro_module_paths: Option<Box<RawValue>>,
95 x_facebook_sources: Option<Box<RawValue>>,
96 #[serde(rename = "debug_id")]
97 debug_id_old: Option<Box<RawValue>>,
98 #[serde(rename = "debugId")]
99 debug_id: Option<Box<RawValue>>,
100}
101
102fn into_rope(value: Option<Box<RawValue>>) -> Option<Rope> {
103 value.map(|v| Rope::from(v.get().as_bytes().to_vec()))
104}
105
106fn escape_content(content: Option<&str>) -> Rope {
108 match content {
109 Some(text) => {
110 Rope::from(serde_json::to_vec(text).expect("string serialization is infallible"))
111 }
112 None => {
113 static NULL: LazyLock<Rope> = LazyLock::new(|| Rope::from("null"));
114 NULL.clone()
115 }
116 }
117}
118
119struct FieldSplit;
124
125#[derive(Debug)]
126struct FieldSplitError(String);
127
128impl std::fmt::Display for FieldSplitError {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 self.0.fmt(f)
131 }
132}
133
134impl std::error::Error for FieldSplitError {}
135
136impl serde::ser::Error for FieldSplitError {
137 fn custom<T: std::fmt::Display>(msg: T) -> Self {
138 FieldSplitError(msg.to_string())
139 }
140}
141
142struct FieldSplitStruct {
143 fields: Vec<(&'static str, Vec<u8>)>,
144}
145
146impl serde::ser::SerializeStruct for FieldSplitStruct {
147 type Ok = Vec<(&'static str, Vec<u8>)>;
148 type Error = FieldSplitError;
149
150 fn serialize_field<T: ?Sized + serde::Serialize>(
151 &mut self,
152 key: &'static str,
153 value: &T,
154 ) -> Result<(), FieldSplitError> {
155 let bytes = serde_json::to_vec(value).map_err(serde::ser::Error::custom)?;
156 self.fields.push((key, bytes));
157 Ok(())
158 }
159
160 fn end(self) -> Result<Self::Ok, FieldSplitError> {
161 Ok(self.fields)
162 }
163}
164
165macro_rules! not_a_struct {
166 ($($f:ident($($arg:ty),*) -> $ret:ty;)*) => {
167 $(fn $f(self, $(_: $arg),*) -> Result<$ret, FieldSplitError> {
168 Err(serde::ser::Error::custom("expected a struct"))
169 })*
170 };
171}
172
173impl serde::Serializer for FieldSplit {
174 type Ok = Vec<(&'static str, Vec<u8>)>;
175 type Error = FieldSplitError;
176 type SerializeSeq = serde::ser::Impossible<Self::Ok, Self::Error>;
177 type SerializeTuple = serde::ser::Impossible<Self::Ok, Self::Error>;
178 type SerializeTupleStruct = serde::ser::Impossible<Self::Ok, Self::Error>;
179 type SerializeTupleVariant = serde::ser::Impossible<Self::Ok, Self::Error>;
180 type SerializeMap = serde::ser::Impossible<Self::Ok, Self::Error>;
181 type SerializeStruct = FieldSplitStruct;
182 type SerializeStructVariant = serde::ser::Impossible<Self::Ok, Self::Error>;
183
184 fn serialize_struct(
185 self,
186 _name: &'static str,
187 len: usize,
188 ) -> Result<FieldSplitStruct, FieldSplitError> {
189 Ok(FieldSplitStruct {
190 fields: Vec::with_capacity(len),
191 })
192 }
193
194 not_a_struct! {
195 serialize_bool(bool) -> Self::Ok;
196 serialize_i8(i8) -> Self::Ok;
197 serialize_i16(i16) -> Self::Ok;
198 serialize_i32(i32) -> Self::Ok;
199 serialize_i64(i64) -> Self::Ok;
200 serialize_u8(u8) -> Self::Ok;
201 serialize_u16(u16) -> Self::Ok;
202 serialize_u32(u32) -> Self::Ok;
203 serialize_u64(u64) -> Self::Ok;
204 serialize_f32(f32) -> Self::Ok;
205 serialize_f64(f64) -> Self::Ok;
206 serialize_char(char) -> Self::Ok;
207 serialize_str(&str) -> Self::Ok;
208 serialize_bytes(&[u8]) -> Self::Ok;
209 serialize_none() -> Self::Ok;
210 serialize_unit() -> Self::Ok;
211 serialize_unit_struct(&'static str) -> Self::Ok;
212 serialize_unit_variant(&'static str, u32, &'static str) -> Self::Ok;
213 serialize_seq(Option<usize>) -> Self::SerializeSeq;
214 serialize_tuple(usize) -> Self::SerializeTuple;
215 serialize_tuple_struct(&'static str, usize) -> Self::SerializeTupleStruct;
216 serialize_tuple_variant(&'static str, u32, &'static str, usize) -> Self::SerializeTupleVariant;
217 serialize_map(Option<usize>) -> Self::SerializeMap;
218 serialize_struct_variant(&'static str, u32, &'static str, usize) -> Self::SerializeStructVariant;
219 }
220
221 fn serialize_some<T: ?Sized + serde::Serialize>(
222 self,
223 _value: &T,
224 ) -> Result<Self::Ok, FieldSplitError> {
225 Err(serde::ser::Error::custom("expected a struct"))
226 }
227
228 fn serialize_newtype_struct<T: ?Sized + serde::Serialize>(
229 self,
230 _name: &'static str,
231 value: &T,
232 ) -> Result<Self::Ok, FieldSplitError> {
233 value.serialize(self)
234 }
235
236 fn serialize_newtype_variant<T: ?Sized + serde::Serialize>(
237 self,
238 _name: &'static str,
239 _variant_index: u32,
240 _variant: &'static str,
241 _value: &T,
242 ) -> Result<Self::Ok, FieldSplitError> {
243 Err(serde::ser::Error::custom("expected a struct"))
244 }
245}
246
247impl StructuredSourceMap {
248 pub fn from_swc_map(mut map: swc_sourcemap::SourceMap) -> Result<Self> {
253 let contents: Vec<Rope> = map
254 .source_contents()
255 .map(|content| escape_content(content.map(|c| c.as_str())))
256 .collect();
257 let has_contents = contents.iter().any(|c| c.to_bytes().as_ref() != b"null");
258 for idx in 0..map.get_source_count() {
259 map.set_source_contents(idx, None);
260 }
261 let mut skeleton = Vec::new();
262 map.to_writer(&mut skeleton)?;
263 let mut result = Self::from_json_slice(&skeleton)?;
264 result.sources_content = has_contents.then_some(SourcesContentField::Escaped(contents));
265 Ok(result)
266 }
267
268 pub fn from_json(map: &Rope) -> Result<Self> {
270 Self::from_json_slice(&map.to_bytes())
271 }
272
273 pub fn from_json_slice(map: &[u8]) -> Result<Self> {
275 let fields: RawFields = serde_json::from_slice(map)?;
276 Ok(StructuredSourceMap {
277 version: into_rope(fields.version),
278 file: into_rope(fields.file),
279 sources: into_rope(fields.sources).map(SourcesField::Raw),
280 source_root: into_rope(fields.source_root),
281 sources_content: into_rope(fields.sources_content).map(SourcesContentField::Raw),
282 sections: into_rope(fields.sections),
283 names: into_rope(fields.names),
284 scopes: into_rope(fields.scopes),
285 range_mappings: into_rope(fields.range_mappings),
286 mappings: into_rope(fields.mappings),
287 ignore_list: into_rope(fields.ignore_list),
288 x_facebook_offsets: into_rope(fields.x_facebook_offsets),
289 x_metro_module_paths: into_rope(fields.x_metro_module_paths),
290 x_facebook_sources: into_rope(fields.x_facebook_sources),
291 debug_id_old: into_rope(fields.debug_id_old),
292 debug_id: into_rope(fields.debug_id),
293 })
294 }
295
296 pub fn from_serialize<T: serde::Serialize>(value: &T) -> Result<Self> {
304 let fields = value
305 .serialize(FieldSplit)
306 .map_err(|error| anyhow::anyhow!("failed to split source map fields: {error}"))?;
307 let mut result = StructuredSourceMap::default();
308 for (key, bytes) in fields {
309 let rope = Rope::from(bytes);
310 match key {
311 "version" => result.version = Some(rope),
312 "file" => result.file = Some(rope),
313 "sources" => result.sources = Some(SourcesField::Raw(rope)),
314 "sourceRoot" => result.source_root = Some(rope),
315 "sourcesContent" => result.sources_content = Some(SourcesContentField::Raw(rope)),
316 "sections" => result.sections = Some(rope),
317 "names" => result.names = Some(rope),
318 "scopes" => result.scopes = Some(rope),
319 "rangeMappings" => result.range_mappings = Some(rope),
320 "mappings" => result.mappings = Some(rope),
321 "ignoreList" => result.ignore_list = Some(rope),
322 "x_facebook_offsets" => result.x_facebook_offsets = Some(rope),
323 "x_metro_module_paths" => result.x_metro_module_paths = Some(rope),
324 "x_facebook_sources" => result.x_facebook_sources = Some(rope),
325 "debug_id" => result.debug_id_old = Some(rope),
326 "debugId" => result.debug_id = Some(rope),
327 other => anyhow::bail!("unknown source map field `{other}`"),
328 }
329 }
330 Ok(result)
331 }
332
333 pub fn rewrite_sources(
339 &self,
340 mut rewrite: impl FnMut(&str) -> Result<Option<String>>,
341 ) -> Result<Self> {
342 let mut result = self.clone();
343 let mut sources: Vec<Option<String>> = match &self.sources {
344 None => return Ok(result),
345 Some(SourcesField::Rewritten(sources)) => sources.clone(),
346 Some(SourcesField::Raw(raw)) => match serde_json::from_slice(&raw.to_bytes()) {
347 Ok(sources) => sources,
348 Err(_) => return Ok(result),
349 },
350 };
351 let mut changed = false;
352 for source in sources.iter_mut().flatten() {
353 if let Some(new_source) = rewrite(source)? {
354 *source = new_source;
355 changed = true;
356 }
357 }
358 if changed {
359 result.sources = Some(SourcesField::Rewritten(sources));
360 }
361 Ok(result)
362 }
363
364 pub fn to_rope(&self) -> Rope {
367 fn field(
371 builder: &mut RopeBuilder,
372 first: &mut bool,
373 key: &'static str,
374 raw_json: Option<&Rope>,
375 ) {
376 if let Some(value) = raw_json {
377 if !*first {
378 *builder += ",";
379 }
380 *first = false;
381 *builder += "\"";
382 *builder += key;
383 *builder += "\":";
384 *builder += value;
385 }
386 }
387
388 let mut builder = RopeBuilder::default();
389 let mut first = true;
390 builder += "{";
391 field(&mut builder, &mut first, "version", self.version.as_ref());
392 field(&mut builder, &mut first, "file", self.file.as_ref());
393 match &self.sources {
394 None => {}
395 Some(SourcesField::Raw(raw)) => {
396 field(&mut builder, &mut first, "sources", Some(raw));
397 }
398 Some(SourcesField::Rewritten(sources)) => {
399 if !first {
400 builder += ",";
401 }
402 first = false;
403 builder += "\"sources\":";
404 builder += &Rope::from(
405 serde_json::to_vec(sources).expect("string serialization is infallible"),
406 );
407 }
408 }
409 field(
410 &mut builder,
411 &mut first,
412 "sourceRoot",
413 self.source_root.as_ref(),
414 );
415 match &self.sources_content {
416 None => {}
417 Some(SourcesContentField::Raw(raw)) => {
418 field(&mut builder, &mut first, "sourcesContent", Some(raw));
419 }
420 Some(SourcesContentField::Escaped(contents)) => {
421 if !first {
422 builder += ",";
423 }
424 first = false;
425 builder += "\"sourcesContent\":[";
426 for (i, content) in contents.iter().enumerate() {
427 if i > 0 {
428 builder += ",";
429 }
430 builder += content;
431 }
432 builder += "]";
433 }
434 }
435 field(&mut builder, &mut first, "sections", self.sections.as_ref());
436 field(&mut builder, &mut first, "names", self.names.as_ref());
437 field(&mut builder, &mut first, "scopes", self.scopes.as_ref());
438 field(
439 &mut builder,
440 &mut first,
441 "rangeMappings",
442 self.range_mappings.as_ref(),
443 );
444 field(&mut builder, &mut first, "mappings", self.mappings.as_ref());
445 field(
446 &mut builder,
447 &mut first,
448 "ignoreList",
449 self.ignore_list.as_ref(),
450 );
451 field(
452 &mut builder,
453 &mut first,
454 "x_facebook_offsets",
455 self.x_facebook_offsets.as_ref(),
456 );
457 field(
458 &mut builder,
459 &mut first,
460 "x_metro_module_paths",
461 self.x_metro_module_paths.as_ref(),
462 );
463 field(
464 &mut builder,
465 &mut first,
466 "x_facebook_sources",
467 self.x_facebook_sources.as_ref(),
468 );
469 field(
470 &mut builder,
471 &mut first,
472 "debug_id",
473 self.debug_id_old.as_ref(),
474 );
475 field(&mut builder, &mut first, "debugId", self.debug_id.as_ref());
476 builder += "}";
477 builder.build()
478 }
479}
480
481impl DeterministicHash for StructuredSourceMap {
482 fn deterministic_hash<H: DeterministicHasher>(&self, state: &mut H) {
483 self.to_rope().deterministic_hash(state);
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
495 fn golden_matches_swc_serializer() -> Result<()> {
496 let mut builder = swc_sourcemap::SourceMapBuilder::new(None);
497 let src_a = builder.add_source("turbopack:///[project]/a.js".into());
498 let src_b = builder.add_source("turbopack:///[project]/dir/b\u{2028}c\"d\\e.js".into());
499 builder.set_source_contents(
500 src_a,
501 Some("let a = 1;\nconsole.log(\"hi\\n\", '\u{1F980}', `\u{2028}\u{0000}`);".into()),
502 );
503 builder.set_source_contents(src_b, None);
504 let name = builder.add_name("console".into());
505 builder.add_raw(0, 0, 0, 0, Some(src_a), Some(name), false);
506 builder.add_raw(0, 10, 1, 0, Some(src_b), None, false);
507 let map = builder.into_sourcemap();
508
509 let mut expected = Vec::new();
510 map.clone().to_writer(&mut expected)?;
511
512 let structured = StructuredSourceMap::from_swc_map(map)?;
513 assert_eq!(structured.to_rope().to_bytes().as_ref(), &expected[..]);
514 Ok(())
515 }
516
517 #[test]
519 fn golden_matches_swc_serializer_no_contents() -> Result<()> {
520 let mut builder = swc_sourcemap::SourceMapBuilder::new(None);
521 let src = builder.add_source("turbopack:///[project]/a.js".into());
522 builder.add_raw(0, 0, 0, 0, Some(src), None, false);
523 let map = builder.into_sourcemap();
524
525 let mut expected = Vec::new();
526 map.clone().to_writer(&mut expected)?;
527
528 let structured = StructuredSourceMap::from_swc_map(map)?;
529 assert_eq!(structured.to_rope().to_bytes().as_ref(), &expected[..]);
530 Ok(())
531 }
532
533 #[test]
535 fn roundtrips_serialized_map() -> Result<()> {
536 let input = r#"{"version":3,"sources":["a.js",null],"sourcesContent":["let a = \"x\";\n",null],"names":["a"],"mappings":"AAAA"}"#;
537 let structured = StructuredSourceMap::from_json(&Rope::from(input))?;
538 assert_eq!(structured.to_rope().to_bytes().as_ref(), input.as_bytes());
539 Ok(())
540 }
541
542 #[test]
547 fn tolerates_undecodable_sources_and_contents() -> Result<()> {
548 let input = r#"{"version":3,"sources":["a.js",7],"sourcesContent":["x\ud800y",42,null],"mappings":"AAAA"}"#;
549 let structured = StructuredSourceMap::from_json(&Rope::from(input))?;
550 assert_eq!(structured.to_rope().to_bytes().as_ref(), input.as_bytes());
551 Ok(())
552 }
553
554 #[test]
556 fn preserves_noncanonical_escaping() -> Result<()> {
557 let input =
558 r#"{"version":3,"sources":["a\/b.js"],"sourcesContent":["c\/dé"],"mappings":"AAAA"}"#;
559 let structured = StructuredSourceMap::from_json(&Rope::from(input))?;
560 assert_eq!(structured.to_rope().to_bytes().as_ref(), input.as_bytes());
561 Ok(())
562 }
563
564 #[test]
567 fn rewrite_leaves_undecodable_sources_untouched() -> Result<()> {
568 let input =
569 r#"{"version":3,"sources":[{"weird":1}],"sourcesContent":["text"],"mappings":"AAAA"}"#;
570 let structured = StructuredSourceMap::from_json(&Rope::from(input))?;
571 let rewritten = structured.rewrite_sources(|_| Ok(Some("nope".to_string())))?;
572 assert_eq!(rewritten.to_rope().to_bytes().as_ref(), input.as_bytes());
573 Ok(())
574 }
575
576 #[test]
579 fn noop_rewrite_is_byte_identical() -> Result<()> {
580 let input =
581 r#"{"version":3,"sources":["a\/b.js"],"sourcesContent":["text"],"mappings":"AAAA"}"#;
582 let structured = StructuredSourceMap::from_json(&Rope::from(input))?;
583 let rewritten = structured.rewrite_sources(|_| Ok(None))?;
584 assert_eq!(rewritten.to_rope().to_bytes().as_ref(), input.as_bytes());
585 Ok(())
586 }
587
588 #[test]
591 fn from_serialize_matches_serialized_roundtrip() -> Result<()> {
592 let cases: &[&str] = &[
593 r#"{"version":3,"sources":["a.js",7],"sourcesContent":["x\ud800y",42,null],"names":["n"],"mappings":"AAAA"}"#,
594 r#"{"version":3,"sources":[],"mappings":""}"#,
595 r#"{"version":3,"file":"out.js","sources":["a\/b.js"],"sourceRoot":"","sourcesContent":["c\/d"],"names":[],"mappings":"AAAA;;AACA","ignoreList":[0]}"#,
596 ];
597 for case in cases {
598 let lazy = swc_sourcemap::lazy::decode(case.as_bytes())?.into_source_map()?;
599 let raw = lazy.into_raw_sourcemap();
600 let expected = serde_json::to_vec(&raw)?;
601 let split = StructuredSourceMap::from_serialize(&raw)?;
602 assert_eq!(
603 split.to_rope().to_bytes().as_ref(),
604 &expected[..],
605 "case: {case}"
606 );
607 let reparsed = StructuredSourceMap::from_json_slice(&expected)?;
608 assert_eq!(split, reparsed, "field-level equality, case: {case}");
609 }
610 Ok(())
611 }
612
613 #[test]
614 fn rewrite_sources_keeps_contents_shared() -> Result<()> {
615 let input = r#"{"version":3,"sources":["turbopack:///[project]/a.js"],"sourcesContent":["text"],"mappings":"AAAA"}"#;
616 let structured = StructuredSourceMap::from_json(&Rope::from(input))?;
617 let rewritten = structured.rewrite_sources(|source| {
618 Ok(source
619 .strip_prefix("turbopack:///[project]/")
620 .map(|rest| format!("file:///root/{rest}")))
621 })?;
622 let json: serde_json::Value = serde_json::from_slice(&rewritten.to_rope().to_bytes())?;
623 assert_eq!(json["sources"][0], "file:///root/a.js");
624 assert_eq!(json["sourcesContent"][0], "text");
625 Ok(())
626 }
627}