diff --git a/Cargo.lock b/Cargo.lock index 7cb7183c54903..547a6bbc9d8fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1969,6 +1969,7 @@ dependencies = [ "base64-simd", "cfg-if", "cow-utils", + "insta", "rayon", "rustc-hash", "serde", diff --git a/crates/oxc_sourcemap/Cargo.toml b/crates/oxc_sourcemap/Cargo.toml index f5ef769f29290..16534f8456538 100644 --- a/crates/oxc_sourcemap/Cargo.toml +++ b/crates/oxc_sourcemap/Cargo.toml @@ -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"] diff --git a/crates/oxc_sourcemap/src/sourcemap_visualizer.rs b/crates/oxc_sourcemap/src/sourcemap_visualizer.rs index e5b426297f546..8637d4755a48b 100644 --- a/crates/oxc_sourcemap/src/sourcemap_visualizer.rs +++ b/crates/oxc_sourcemap/src/sourcemap_visualizer.rs @@ -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>>> = self .sourcemap .get_sources() @@ -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], 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> { let mut tables = vec![]; let mut line_byte_offset = 0; @@ -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::>()); - line_byte_offset = i; + tables.push(content[line_byte_offset..=i].encode_utf16().collect::>()); + line_byte_offset = i + 1; } _ => {} } @@ -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" -"# - ); - } -} diff --git a/crates/oxc_sourcemap/tests/fixtures/basic/test.js b/crates/oxc_sourcemap/tests/fixtures/basic/test.js new file mode 100644 index 0000000000000..12b22c059b158 --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/basic/test.js @@ -0,0 +1,7 @@ + +// shared.js +const a = 'shared.js'; + +// index.js +const a$1 = 'index.js'; +console.log(a$1, a); diff --git a/crates/oxc_sourcemap/tests/fixtures/basic/test.js.map b/crates/oxc_sourcemap/tests/fixtures/basic/test.js.map new file mode 100644 index 0000000000000..41759e9ffd4c3 --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/basic/test.js.map @@ -0,0 +1,7 @@ +{ + "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" +} diff --git a/crates/oxc_sourcemap/tests/fixtures/basic/visualizer.snap b/crates/oxc_sourcemap/tests/fixtures/basic/visualizer.snap new file mode 100644 index 0000000000000..4f52cfd39cf04 --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/basic/visualizer.snap @@ -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" diff --git a/crates/oxc_sourcemap/tests/fixtures/esbuild/README.md b/crates/oxc_sourcemap/tests/fixtures/esbuild/README.md new file mode 100644 index 0000000000000..437cd10c655fc --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/esbuild/README.md @@ -0,0 +1 @@ +Example code from https://github.com/evanw/source-map-visualization/ diff --git a/crates/oxc_sourcemap/tests/fixtures/esbuild/example.js b/crates/oxc_sourcemap/tests/fixtures/esbuild/example.js new file mode 100644 index 0000000000000..86903998b466e --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/esbuild/example.js @@ -0,0 +1,45 @@ +// index.tsx +import { h as u, Fragment as l, render as c } from "preact"; + +// counter.tsx +import { h as t, Component as i } from "preact"; +import { useState as a } from "preact/hooks"; +var n = class extends i { + constructor(e) { + super(e); + this.n = () => this.setState({ t: this.state.t + 1 }); + this.r = () => this.setState({ t: this.state.t - 1 }); + this.state.t = e.e; + } + render() { + return t("div", { + class: "counter" + }, t("h1", null, this.props.label), t("p", null, t("button", { + onClick: this.r + }, "-"), " ", this.state.t, " ", t("button", { + onClick: this.n + }, "+"))); + } +}, s = (r) => { + let [o, e] = a(r.e); + return t("div", { + class: "counter" + }, t("h1", null, r.o), t("p", null, t("button", { + onClick: () => e(o - 1) + }, "-"), " ", o, " ", t("button", { + onClick: () => e(o + 1) + }, "+"))); +}; + +// index.tsx +c( + u(l, null, u(n, { + o: "Counter 1", + e: 100 + }), u(s, { + o: "Counter 2", + e: 200 + })), + document.getElementById("root") +); +//# sourceMappingURL=example.js.map diff --git a/crates/oxc_sourcemap/tests/fixtures/esbuild/example.js.map b/crates/oxc_sourcemap/tests/fixtures/esbuild/example.js.map new file mode 100644 index 0000000000000..f8f5027e47dfc --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/esbuild/example.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["index.tsx", "counter.tsx"], + "sourcesContent": ["import { h, Fragment, render } from 'preact'\nimport { CounterClass, CounterFunction } from './counter'\n\nrender(\n <>\n \n \n ,\n document.getElementById('root')!,\n)\n", "import { h, Component } from 'preact'\nimport { useState } from 'preact/hooks'\n\ninterface CounterProps {\n label_: string\n initialValue_: number\n}\n\ninterface CounterState {\n value_: number\n}\n\nexport class CounterClass extends Component {\n state: CounterState\n\n constructor(props: CounterProps) {\n super(props)\n this.state.value_ = props.initialValue_\n }\n\n increment_ = () => this.setState({ value_: this.state.value_ + 1 })\n decrement_ = () => this.setState({ value_: this.state.value_ - 1 })\n\n render() {\n return
\n

{this.props.label}

\n

\n \n {' '}\n {this.state.value_}\n {' '}\n \n

\n
\n }\n}\n\nexport let CounterFunction = (props: CounterProps) => {\n let [value, setValue] = useState(props.initialValue_)\n return
\n

{props.label_}

\n

\n \n {' '}\n {value}\n {' '}\n \n

\n
\n}\n"], + "mappings": ";AAAA,SAAS,KAAAA,GAAG,YAAAC,GAAU,UAAAC,SAAc;;;ACApC,SAAS,KAAAC,GAAG,aAAAC,SAAiB;AAC7B,SAAS,YAAAC,SAAgB;AAWlB,IAAMC,IAAN,cAA2BF,EAAsC;AAAA,EAGtE,YAAYG,GAAqB;AAC/B,UAAMA,CAAK;AAIb,SAAAC,IAAa,MAAM,KAAK,SAAS,EAAEC,GAAQ,KAAK,MAAMA,IAAS,EAAE,CAAC;AAClE,SAAAC,IAAa,MAAM,KAAK,SAAS,EAAED,GAAQ,KAAK,MAAMA,IAAS,EAAE,CAAC;AAJhE,SAAK,MAAMA,IAASF,EAAMI;AAAA,EAC5B;AAAA,EAKA,SAAS;AACP,WAAOR,EAAC;AAAA,MAAI,OAAM;AAAA,OAChBA,EAAC,YAAI,KAAK,MAAM,KAAM,GACtBA,EAAC,WACCA,EAAC;AAAA,MAAO,SAAS,KAAKO;AAAA,OAAY,GAAC,GAClC,KACA,KAAK,MAAMD,GACX,KACDN,EAAC;AAAA,MAAO,SAAS,KAAKK;AAAA,OAAY,GAAC,CACrC,CACF;AAAA,EACF;AACF,GAEWI,IAAkB,CAACL,MAAwB;AACpD,MAAI,CAACM,GAAOC,CAAQ,IAAIT,EAASE,EAAMI,CAAa;AACpD,SAAOR,EAAC;AAAA,IAAI,OAAM;AAAA,KAChBA,EAAC,YAAII,EAAMQ,CAAO,GAClBZ,EAAC,WACCA,EAAC;AAAA,IAAO,SAAS,MAAMW,EAASD,IAAQ,CAAC;AAAA,KAAG,GAAC,GAC5C,KACAA,GACA,KACDV,EAAC;AAAA,IAAO,SAAS,MAAMW,EAASD,IAAQ,CAAC;AAAA,KAAG,GAAC,CAC/C,CACF;AACF;;;AD9CAG;AAAA,EACEC,EAAAC,GAAA,MACED,EAACE,GAAA;AAAA,IAAaC,GAAO;AAAA,IAAYC,GAAe;AAAA,GAAK,GACrDJ,EAACK,GAAA;AAAA,IAAgBF,GAAO;AAAA,IAAYC,GAAe;AAAA,GAAK,CAC1D;AAAA,EACA,SAAS,eAAe,MAAM;AAChC;", + "names": ["h", "Fragment", "render", "h", "Component", "useState", "CounterClass", "props", "increment_", "value_", "decrement_", "initialValue_", "CounterFunction", "value", "setValue", "label_", "render", "h", "Fragment", "CounterClass", "label_", "initialValue_", "CounterFunction"] +} diff --git a/crates/oxc_sourcemap/tests/fixtures/esbuild/visualizer.snap b/crates/oxc_sourcemap/tests/fixtures/esbuild/visualizer.snap new file mode 100644 index 0000000000000..65ad6a967cc12 --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/esbuild/visualizer.snap @@ -0,0 +1,208 @@ +--- +source: crates/oxc_sourcemap/tests/main.rs +input_file: crates/oxc_sourcemap/tests/fixtures/esbuild/example.js +snapshot_kind: text +--- +- index.tsx +(0:0) "import { " --> (1:0) "import { " +(0:9) "h, " --> (1:9) "h as " +(0:9) "h, " --> (1:14) "u, " +(0:12) "Fragment, " --> (1:17) "Fragment as " +(0:12) "Fragment, " --> (1:29) "l, " +(0:22) "render } from " --> (1:32) "render as " +(0:22) "render } from " --> (1:42) "c } from " +(0:36) "'preact'\n" --> (1:51) "\"preact\";\n" +- counter.tsx +(0:0) "import { " --> (4:0) "import { " +(0:9) "h, " --> (4:9) "h as " +(0:9) "h, " --> (4:14) "t, " +(0:12) "Component } from " --> (4:17) "Component as " +(0:12) "Component } from " --> (4:30) "i } from " +(0:29) "'preact'\n" --> (4:39) "\"preact\";\n" +(1:0) "import { " --> (5:0) "import { " +(1:9) "useState } from " --> (5:9) "useState as " +(1:9) "useState } from " --> (5:21) "a } from " +(1:25) "'preact/hooks'\n" --> (5:30) "\"preact/hooks\";\n" +(12:7) "class " --> (6:0) "var " +(12:13) "CounterClass extends " --> (6:4) "n = " +(12:7) "class CounterClass extends " --> (6:8) "class extends " +(12:34) "Component " --> (6:22) "i " +(12:72) "{\n" --> (6:24) "{\n" +(12:72) "{\n" --> (7:0) " " +(15:2) "constructor(" --> (7:2) "constructor(" +(15:14) "props: CounterProps) " --> (7:14) "e) " +(15:35) "{\n" --> (7:17) "{\n" +(16:4) "super(" --> (8:0) " super(" +(16:10) "props" --> (8:10) "e" +(16:15) ")\n" --> (8:11) ");\n" +(20:2) "increment_ = " --> (9:0) " this." +(20:2) "increment_ = " --> (9:9) "n = " +(20:15) "() => " --> (9:13) "() => " +(20:21) "this." --> (9:19) "this." +(20:26) "setState(" --> (9:24) "setState(" +(20:35) "{ " --> (9:33) "{ " +(20:37) "value_: " --> (9:35) "t: " +(20:45) "this." --> (9:38) "this." +(20:50) "state." --> (9:43) "state." +(20:56) "value_ + " --> (9:49) "t + " +(20:65) "1 " --> (9:53) "1 " +(20:67) "}" --> (9:55) "}" +(20:68) ")\n" --> (9:56) ");\n" +(21:2) "decrement_ = " --> (10:0) " this." +(21:2) "decrement_ = " --> (10:9) "r = " +(21:15) "() => " --> (10:13) "() => " +(21:21) "this." --> (10:19) "this." +(21:26) "setState(" --> (10:24) "setState(" +(21:35) "{ " --> (10:33) "{ " +(21:37) "value_: " --> (10:35) "t: " +(21:45) "this." --> (10:38) "this." +(21:50) "state." --> (10:43) "state." +(21:56) "value_ - " --> (10:49) "t - " +(21:65) "1 " --> (10:53) "1 " +(21:67) "}" --> (10:55) "}" +(21:68) ")\n" --> (10:56) ");\n" +(17:4) "this." --> (11:0) " this." +(17:9) "state." --> (11:9) "state." +(17:15) "value_ = " --> (11:15) "t = " +(17:24) "props." --> (11:19) "e." +(17:30) "initialValue_\n" --> (11:21) "e;\n" +(17:30) "initialValue_\n" --> (12:0) " " +(18:2) "}\n" --> (12:2) "}\n" +(18:2) "}\n" --> (13:0) " " +(23:2) "render() " --> (13:2) "render() " +(23:11) "{\n" --> (13:11) "{\n" +(24:4) "return " --> (14:0) " return " +(24:11) "<" --> (14:11) "t(" +(24:12) "div " --> (14:13) "\"div\", {\n" +(24:12) "div " --> (15:0) " " +(24:16) "class=" --> (15:6) "class: " +(24:22) "\"counter\">\n" --> (15:13) "\"counter\"\n" +(24:22) "\"counter\">\n" --> (16:0) " }, " +(25:6) "<" --> (16:7) "t(" +(25:7) "h1>{" --> (16:9) "\"h1\", null, " +(25:11) "this." --> (16:21) "this." +(25:16) "props." --> (16:26) "props." +(25:22) "label}" --> (16:32) "label" +(25:28) "\n" --> (16:37) "), " +(26:6) "<" --> (16:40) "t(" +(26:7) "p>\n" --> (16:42) "\"p\", null, " +(27:8) "<" --> (16:53) "t(" +(27:9) "button " --> (16:55) "\"button\", {\n" +(27:9) "button " --> (17:0) " " +(27:16) "onClick={" --> (17:6) "onClick: " +(27:25) "this." --> (17:15) "this." +(27:30) "decrement_}>" --> (17:20) "r\n" +(27:30) "decrement_}>" --> (18:0) " }, " +(27:42) "-" --> (18:7) "\"-\"" +(27:43) "\n" --> (18:10) "), " +(28:9) "' '}\n" --> (18:13) "\" \", " +(29:9) "this." --> (18:18) "this." +(29:14) "state." --> (18:23) "state." +(29:20) "value_}\n" --> (18:29) "t, " +(30:9) "' '}\n" --> (18:32) "\" \", " +(31:8) "<" --> (18:37) "t(" +(31:9) "button " --> (18:39) "\"button\", {\n" +(31:9) "button " --> (19:0) " " +(31:16) "onClick={" --> (19:6) "onClick: " +(31:25) "this." --> (19:15) "this." +(31:30) "increment_}>" --> (19:20) "n\n" +(31:30) "increment_}>" --> (20:0) " }, " +(31:42) "+" --> (20:7) "\"+\"" +(31:43) "\n" --> (20:10) ")" +(32:6) "

\n" --> (20:11) ")" +(33:4) "\n" --> (20:12) ");\n" +(33:4) "\n" --> (21:0) " " +(34:2) "}\n" --> (21:2) "}\n" +(35:0) "}\n" --> (22:0) "}, " +(37:11) "CounterFunction = " --> (22:3) "s = " +(37:29) "(" --> (22:7) "(" +(37:30) "props: CounterProps) => " --> (22:8) "r) => " +(37:54) "{\n" --> (22:14) "{\n" +(38:2) "let " --> (23:0) " let " +(38:6) "[" --> (23:6) "[" +(38:7) "value, " --> (23:7) "o, " +(38:14) "setValue" --> (23:10) "e" +(38:22) "] = " --> (23:11) "] = " +(38:26) "useState(" --> (23:15) "a(" +(38:35) "props." --> (23:17) "r." +(38:41) "initialValue_" --> (23:19) "e" +(38:54) ")\n" --> (23:20) ");\n" +(39:2) "return " --> (24:0) " return " +(39:9) "<" --> (24:9) "t(" +(39:10) "div " --> (24:11) "\"div\", {\n" +(39:10) "div " --> (25:0) " " +(39:14) "class=" --> (25:4) "class: " +(39:20) "\"counter\">\n" --> (25:11) "\"counter\"\n" +(39:20) "\"counter\">\n" --> (26:0) " }, " +(40:4) "<" --> (26:5) "t(" +(40:5) "h1>{" --> (26:7) "\"h1\", null, " +(40:9) "props." --> (26:19) "r." +(40:15) "label_}" --> (26:21) "o" +(40:22) "\n" --> (26:22) "), " +(41:4) "<" --> (26:25) "t(" +(41:5) "p>\n" --> (26:27) "\"p\", null, " +(42:6) "<" --> (26:38) "t(" +(42:7) "button " --> (26:40) "\"button\", {\n" +(42:7) "button " --> (27:0) " " +(42:14) "onClick={" --> (27:4) "onClick: " +(42:23) "() => " --> (27:13) "() => " +(42:29) "setValue(" --> (27:19) "e(" +(42:38) "value - " --> (27:21) "o - " +(42:46) "1" --> (27:25) "1" +(42:47) ")}>" --> (27:26) ")\n" +(42:47) ")}>" --> (28:0) " }, " +(42:50) "-" --> (28:5) "\"-\"" +(42:51) "\n" --> (28:8) "), " +(43:7) "' '}\n" --> (28:11) "\" \", " +(44:7) "value}\n" --> (28:16) "o, " +(45:7) "' '}\n" --> (28:19) "\" \", " +(46:6) "<" --> (28:24) "t(" +(46:7) "button " --> (28:26) "\"button\", {\n" +(46:7) "button " --> (29:0) " " +(46:14) "onClick={" --> (29:4) "onClick: " +(46:23) "() => " --> (29:13) "() => " +(46:29) "setValue(" --> (29:19) "e(" +(46:38) "value + " --> (29:21) "o + " +(46:46) "1" --> (29:25) "1" +(46:47) ")}>" --> (29:26) ")\n" +(46:47) ")}>" --> (30:0) " }, " +(46:50) "+" --> (30:5) "\"+\"" +(46:51) "\n" --> (30:8) ")" +(47:4) "

\n" --> (30:9) ")" +(48:2) "\n" --> (30:10) ");\n" +(49:0) "}\n" --> (31:0) "};\n" +- index.tsx +(3:0) "render(\n" --> (34:0) "c(\n" +(3:0) "render(\n" --> (35:0) " " +(4:2) "<>\n" --> (35:2) "u(" +(4:2) "<>\n" --> (35:4) "l, " +(4:2) "<>\n" --> (35:7) "null, " +(5:4) "<" --> (35:13) "u(" +(5:5) "CounterClass " --> (35:15) "n, " +(5:5) "CounterClass " --> (35:18) "{\n" +(5:5) "CounterClass " --> (36:0) " " +(5:18) "label_=" --> (36:4) "o: " +(5:25) "\"Counter 1\" " --> (36:7) "\"Counter 1\",\n" +(5:25) "\"Counter 1\" " --> (37:0) " " +(5:37) "initialValue_={" --> (37:4) "e: " +(5:52) "100} " --> (37:7) "100\n" +(5:52) "100} " --> (38:0) " }" +(5:57) "/>\n" --> (38:3) "), " +(6:4) "<" --> (38:6) "u(" +(6:5) "CounterFunction " --> (38:8) "s, " +(6:5) "CounterFunction " --> (38:11) "{\n" +(6:5) "CounterFunction " --> (39:0) " " +(6:21) "label_=" --> (39:4) "o: " +(6:28) "\"Counter 2\" " --> (39:7) "\"Counter 2\",\n" +(6:28) "\"Counter 2\" " --> (40:0) " " +(6:40) "initialValue_={" --> (40:4) "e: " +(6:55) "200} " --> (40:7) "200\n" +(6:55) "200} " --> (41:0) " }" +(6:60) "/>\n" --> (41:3) ")" +(7:2) ",\n" --> (41:4) "),\n" +(7:2) ",\n" --> (42:0) " " +(8:2) "document." --> (42:2) "document." +(8:11) "getElementById(" --> (42:11) "getElementById(" +(8:26) "'root'" --> (42:26) "\"root\"" +(8:32) ")!,\n" --> (42:32) ")\n" +(9:0) ")\n" --> (43:0) ");\n" diff --git a/crates/oxc_sourcemap/tests/fixtures/swap/test.js b/crates/oxc_sourcemap/tests/fixtures/swap/test.js new file mode 100644 index 0000000000000..759a61de1c12f --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/swap/test.js @@ -0,0 +1,3 @@ +z; +y; +x; diff --git a/crates/oxc_sourcemap/tests/fixtures/swap/test.js.map b/crates/oxc_sourcemap/tests/fixtures/swap/test.js.map new file mode 100644 index 0000000000000..bbcfe11bc8c04 --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/swap/test.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "names": ["z", "y", "x"], + "sources": ["test.js"], + "sourcesContent": ["x;\ny;\nz;\n"], + "mappings": "AAEAA,CAAC;AADDC,CAAC;AADDC,CAAC", + "ignoreList": [] +} diff --git a/crates/oxc_sourcemap/tests/fixtures/swap/visualizer.snap b/crates/oxc_sourcemap/tests/fixtures/swap/visualizer.snap new file mode 100644 index 0000000000000..75241b81d2720 --- /dev/null +++ b/crates/oxc_sourcemap/tests/fixtures/swap/visualizer.snap @@ -0,0 +1,12 @@ +--- +source: crates/oxc_sourcemap/tests/main.rs +input_file: crates/oxc_sourcemap/tests/fixtures/swap/test.js +snapshot_kind: text +--- +- test.js +(2:0) "z" --> (0:0) "z" +(2:1) ";\n" --> (0:1) ";\n" +(1:0) "y" --> (1:0) "y" +(1:1) ";\n" --> (1:1) ";\n" +(0:0) "x" --> (2:0) "x" +(0:1) ";\n" --> (2:1) ";\n" diff --git a/crates/oxc_sourcemap/tests/main.rs b/crates/oxc_sourcemap/tests/main.rs new file mode 100644 index 0000000000000..973ef5791ef20 --- /dev/null +++ b/crates/oxc_sourcemap/tests/main.rs @@ -0,0 +1,17 @@ +use std::fs; + +use oxc_sourcemap::{SourceMap, SourcemapVisualizer}; + +#[test] +fn snapshot_sourcemap_visualizer() { + insta::glob!("fixtures/**/*.js", |path| { + let js = fs::read_to_string(path).unwrap(); + let js_map = fs::read_to_string(path.with_extension("js.map")).unwrap(); + let sourcemap = SourceMap::from_json_string(&js_map).unwrap(); + let visualizer = SourcemapVisualizer::new(&js, &sourcemap); + let visualizer_text = visualizer.into_visualizer_text(); + insta::with_settings!({ snapshot_path => path.parent().unwrap(), prepend_module_to_snapshot => false, snapshot_suffix => "", omit_expression => true }, { + insta::assert_snapshot!("visualizer", visualizer_text); + }); + }); +}