Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/oxc_sourcemap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ serde_json = { workspace = true }

rayon = { workspace = true, optional = true }

[dev-dependencies]
insta = { workspace = true, features = ["glob"] }

[features]
default = []
concurrent = ["dep:rayon"]
186 changes: 62 additions & 124 deletions crates/oxc_sourcemap/src/sourcemap_visualizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ impl<'a> SourcemapVisualizer<'a> {

#[allow(clippy::cast_possible_truncation)]
pub fn into_visualizer_text(self) -> String {
let mut source_log_map = FxHashMap::default();
let source_contents_lines_map: FxHashMap<String, Option<Vec<Vec<u16>>>> = self
.sourcemap
.get_sources()
Expand All @@ -37,103 +36,78 @@ impl<'a> SourcemapVisualizer<'a> {

let mut s = String::new();

self.sourcemap.get_tokens().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(|| {
s.push('-');
s.push(' ');
s.push_str(source);
s.push('\n');
true
});

// Print token
if pre_token.get_source_id() == token.get_source_id() {
s.push_str(&format!(
"({}:{}-{}:{}) {:?}",
pre_token.get_src_line(),
pre_token.get_src_col(),
token.get_src_line(),
token.get_src_col(),
Self::str_slice_by_token(
source_contents_lines,
(pre_token.get_src_line(), pre_token.get_src_col()),
(token.get_src_line(), token.get_src_col())
)
));
} else if token.get_source_id().is_some() {
Self::print_source_last_mapping(
&mut s,
source_contents_lines,
(pre_token.get_src_line(), pre_token.get_src_col()),
);
let tokens = &self.sourcemap.tokens;

let mut last_source: Option<&str> = None;
for i in 0..tokens.len() {
let t = &tokens[i];
let Some(source_id) = t.source_id else { continue };
let Some(source) = self.sourcemap.get_source(source_id) else { continue };
let Some(source_contents_lines) = source_contents_lines_map[source].as_ref() else {
continue;
};

// find next dst column or EOL
let dst_end_col = {
match tokens.get(i + 1) {
Some(t2) if t2.dst_line == t.dst_line => t2.dst_col,
_ => output_lines[t.dst_line as usize].len() as u32,
}
};

// find next src column or EOL
let src_end_col = 'result: {
for t2 in &tokens[i + 1..] {
if t2.source_id == t.source_id && t2.src_line == t.src_line {
// skip duplicate or backward
if t2.src_col <= t.src_col {
continue;
}
break 'result t2.src_col;
}

s.push_str(" --> ");

s.push_str(&format!(
"({}:{}-{}:{}) {:?}",
pre_token.get_dst_line(),
pre_token.get_dst_col(),
token.get_dst_line(),
token.get_dst_col(),
Self::str_slice_by_token(
&output_lines,
(pre_token.get_dst_line(), pre_token.get_dst_col(),),
(token.get_dst_line(), token.get_dst_col(),)
)
));
s.push('\n');
break;
}
source_contents_lines[t.src_line as usize].len() as u32
};

// Print source
if last_source != Some(source) {
s.push('-');
s.push(' ');
s.push_str(source);
s.push('\n');
last_source = Some(source);
}

token
});

if let Some(last_token) =
self.sourcemap.get_token(self.sourcemap.get_tokens().count() as u32 - 1)
{
if let Some(Some(source_contents_lines)) = last_token
.get_source_id()
.and_then(|id| self.sourcemap.get_source(id))
.and_then(|source| source_contents_lines_map.get(source))
{
Self::print_source_last_mapping(
&mut s,
s.push_str(&format!(
"({}:{}) {:?}",
t.src_line,
t.src_col,
Self::str_slice_by_token(
source_contents_lines,
(last_token.get_src_line(), last_token.get_src_col()),
);
}
(t.src_line, t.src_col),
(t.src_line, src_end_col)
)
));

s.push_str(" --> ");
Self::print_source_last_mapping(
&mut s,
&output_lines,
(last_token.get_dst_line(), last_token.get_dst_col()),
);

s.push_str(&format!(
"({}:{}) {:?}",
t.dst_line,
t.dst_col,
Self::str_slice_by_token(
&output_lines,
(t.dst_line, t.dst_col),
(t.dst_line, dst_end_col)
)
));
s.push('\n');
}

s
}

#[allow(clippy::cast_possible_truncation)]
fn print_source_last_mapping(s: &mut String, buff: &[Vec<u16>], start: (u32, u32)) {
let line = if buff.is_empty() { 0 } else { buff.len() as u32 - 1 };
let column = buff.last().map(|v| v.len() as u32).unwrap_or_default();
s.push_str(&format!(
"({}:{}-{}:{}) {:?}",
start.0,
start.1,
line,
column,
Self::str_slice_by_token(buff, start, (line, column))
));
}

fn generate_line_utf16_tables(content: &str) -> Vec<Vec<u16>> {
let mut tables = vec![];
let mut line_byte_offset = 0;
Expand All @@ -144,8 +118,8 @@ impl<'a> SourcemapVisualizer<'a> {
if ch == '\r' && content.chars().nth(i + 1) == Some('\n') {
continue;
}
tables.push(content[line_byte_offset..i].encode_utf16().collect::<Vec<_>>());
line_byte_offset = i;
tables.push(content[line_byte_offset..=i].encode_utf16().collect::<Vec<_>>());
line_byte_offset = i + 1;
}
_ => {}
}
Expand Down Expand Up @@ -186,39 +160,3 @@ impl<'a> SourcemapVisualizer<'a> {
Cow::Owned(replaced.into_owned())
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn should_work() {
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"
}"#).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();
assert_eq!(
visualizer_text,
r#"- shared.js
(0:0-0:6) "const " --> (2:0-2:6) "\nconst"
(0:6-0:10) "a = " --> (2:6-2:10) " a ="
(0:10-2:13) "'shared.js'\n\nexport { a }" --> (2:10-5:0) " 'shared.js';\n\n// index.js"
- index.js
(1:0-1:6) "\nconst" --> (5:0-5:6) "\nconst"
(1:6-1:10) " a =" --> (5:6-5:12) " a$1 ="
(1:10-2:0) " 'index.js'" --> (5:12-6:0) " 'index.js';"
(2:0-2:8) "\nconsole" --> (6:0-6:8) "\nconsole"
(2:8-2:12) ".log" --> (6:8-6:12) ".log"
(2:12-2:15) "(a," --> (6:12-6:17) "(a$1,"
(2:15-2:18) " a2" --> (6:17-6:19) " a"
(2:18-3:1) ")\n" --> (6:19-7:1) ");\n"
"#
);
}
}
7 changes: 7 additions & 0 deletions crates/oxc_sourcemap/tests/fixtures/basic/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

// shared.js
const a = 'shared.js';

// index.js
const a$1 = 'index.js';
console.log(a$1, a);
7 changes: 7 additions & 0 deletions crates/oxc_sourcemap/tests/fixtures/basic/test.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions crates/oxc_sourcemap/tests/fixtures/basic/visualizer.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: crates/oxc_sourcemap/tests/main.rs
input_file: crates/oxc_sourcemap/tests/fixtures/basic/test.js
snapshot_kind: text
---
- shared.js
(0:0) "const " --> (2:0) "const "
(0:6) "a = " --> (2:6) "a = "
(0:10) "'shared.js'\n" --> (2:10) "'shared.js';\n"
- index.js
(1:0) "const " --> (5:0) "const "
(1:6) "a = " --> (5:6) "a$1 = "
(1:10) "'index.js'\n" --> (5:12) "'index.js';\n"
(2:0) "console." --> (6:0) "console."
(2:8) "log(" --> (6:8) "log("
(2:12) "a, " --> (6:12) "a$1, "
(2:15) "a2)" --> (6:17) "a)"
(2:18) "\n" --> (6:19) ";\n"
1 change: 1 addition & 0 deletions crates/oxc_sourcemap/tests/fixtures/esbuild/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Example code from https://github.com/evanw/source-map-visualization/
45 changes: 45 additions & 0 deletions crates/oxc_sourcemap/tests/fixtures/esbuild/example.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/oxc_sourcemap/tests/fixtures/esbuild/example.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading