diff --git a/crates/oxc_codegen/tests/integration/snapshots/stacktrace_is_correct.snap b/crates/oxc_codegen/tests/integration/snapshots/stacktrace_is_correct.snap new file mode 100644 index 0000000000000..5dd10f892a12a --- /dev/null +++ b/crates/oxc_codegen/tests/integration/snapshots/stacktrace_is_correct.snap @@ -0,0 +1,213 @@ +--- +source: crates/oxc_codegen/tests/integration/sourcemap.rs +--- +## Input +const fn = () => { + Error.stackTraceLimit = 2; + throw new Error() +}; +fn() + +## Output +const fn = () => { + Error.stackTraceLimit = 2; + throw new Error(); +}; +fn(); + + +## Stderr +/project/input.js:3 + throw new Error() + ^ + + +Error + at fn (/project/input.js:3:11) + at (/project/input.js:5:1) + +------------------------------------------------------ +## Input +const obj = { + fn() { + Error.stackTraceLimit = 2; + throw new Error() + } +} +obj.fn() + +## Output +const obj = { fn() { + Error.stackTraceLimit = 2; + throw new Error(); +} }; +obj.fn(); + + +## Stderr +/project/input.js:4 + throw new Error() + ^ + + +Error + at Object.fn (/project/input.js:4:15) + at (/project/input.js:7:5) + +------------------------------------------------------ +## Input +const obj = { + obj2: { + fn() { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.obj2.fn() + +## Output +const obj = { obj2: { fn() { + Error.stackTraceLimit = 2; + throw new Error(); +} } }; +obj.obj2.fn(); + + +## Stderr +/project/input.js:5 + throw new Error() + ^ + + +Error + at Object.fn (/project/input.js:5:19) + at (/project/input.js:9:10) + +------------------------------------------------------ +## Input +const obj = { + fn() { + return function fn2() { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.fn()() + +## Output +const obj = { fn() { + return function fn2() { + Error.stackTraceLimit = 2; + throw new Error(); + }; +} }; +obj.fn()(); + + +## Stderr +/project/input.js:5 + throw new Error() + ^ + + +Error + at fn2 (/project/input.js:5:19) + at (/project/input.js:9:5) + +------------------------------------------------------ +## Input +const obj = { + fn() { + return () => { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.fn([1])() + +## Output +const obj = { fn() { + return () => { + Error.stackTraceLimit = 2; + throw new Error(); + }; +} }; +obj.fn([1])(); + + +## Stderr +/project/input.js:5 + throw new Error() + ^ + + +Error + at (/project/input.js:5:19) + at (/project/input.js:9:9) + +------------------------------------------------------ +## Input +var a +const obj = { + fn() { + return () => { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.fn({a})() + +## Output +var a; +const obj = { fn() { + return () => { + Error.stackTraceLimit = 2; + throw new Error(); + }; +} }; +obj.fn({ a })(); + + +## Stderr +/project/input.js:6 + throw new Error() + ^ + + +Error + at (/project/input.js:6:19) + at (/project/input.js:10:9) + +------------------------------------------------------ +## Input +const fn = (name, cb) => { + cb() +} +fn('name', () => { + Error.stackTraceLimit = 2; + throw new Error() +}) + +## Output +const fn = (name, cb) => { + cb(); +}; +fn("name", () => { + Error.stackTraceLimit = 2; + throw new Error(); +}); + + +## Stderr +/project/input.js:6 + throw new Error() + ^ + + +Error + at (/project/input.js:6:11) + at fn (/project/input.js:2:5) diff --git a/crates/oxc_codegen/tests/integration/sourcemap.rs b/crates/oxc_codegen/tests/integration/sourcemap.rs index 9bf41e73574a5..02aca7746e13e 100644 --- a/crates/oxc_codegen/tests/integration/sourcemap.rs +++ b/crates/oxc_codegen/tests/integration/sourcemap.rs @@ -1,6 +1,9 @@ +use std::{env, path::PathBuf}; + +use cow_utils::CowUtils; use oxc_allocator::Allocator; use oxc_ast::ast::{Expression, Statement}; -use oxc_codegen::Codegen; +use oxc_codegen::{Codegen, CodegenOptions}; use oxc_parser::Parser; use oxc_span::{SourceType, Span}; @@ -27,3 +30,114 @@ fn incorrect_ast() { let ret = Codegen::new().with_options(default_options()).build(&program); assert!(ret.map.is_some(), "sourcemap exists"); } + +#[test] +fn stacktrace_is_correct() { + let cases = &[ + "\ +const fn = () => { + Error.stackTraceLimit = 2; + throw new Error() +}; +fn()", + "\ +const obj = { + fn() { + Error.stackTraceLimit = 2; + throw new Error() + } +} +obj.fn()", + "\ +const obj = { + obj2: { + fn() { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.obj2.fn()", + "\ +const obj = { + fn() { + return function fn2() { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.fn()()", + "\ +const obj = { + fn() { + return () => { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.fn([1])()", + "\ +var a +const obj = { + fn() { + return () => { + Error.stackTraceLimit = 2; + throw new Error() + } + } +} +obj.fn({a})()", + "\ +const fn = (name, cb) => { + cb() +} +fn('name', () => { + Error.stackTraceLimit = 2; + throw new Error() +})", + ]; + + insta::with_settings!({ prepend_module_to_snapshot => false, snapshot_suffix => "", omit_expression => true }, { + insta::assert_snapshot!( + "stacktrace_is_correct", + cases.iter().map(|s| { + let (output, sourcemap_url) = codegen(s); + format!("## Input\n{}\n\n## Output\n{}\n\n## Stderr\n{}", s, output, execute_with_node(&output, &sourcemap_url)) + }).collect::>().join("\n------------------------------------------------------\n") + ); + }); +} + +fn codegen(code: &str) -> (String, String) { + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, code, SourceType::mjs()).parse(); + let ret = Codegen::new() + .with_options(CodegenOptions { + source_map_path: Some(PathBuf::from("input.js")), + ..Default::default() + }) + .build(&ret.program); + (ret.code, ret.map.unwrap().to_data_url()) +} + +fn execute_with_node(code: &str, sourcemap_url: &str) -> String { + let cwd = env::current_dir().unwrap().join("input.js"); + let cwd = cwd.to_str().unwrap(); + + let code = format!("{code}\n//# sourceMappingURL={sourcemap_url}\n"); + + let output = std::process::Command::new("node") + .arg("--enable-source-maps") + .args(["--input-type", "module"]) + .args(["--eval", &code]) + .output() + .unwrap(); + String::from_utf8_lossy(&output.stderr) + .cow_replace(cwd, "/project/input.js") + .lines() + .filter(|line| !line.starts_with("Node.js v")) + .collect::>() + .join("\n") +}