Skip to content

Commit fb358d9

Browse files
committed
Emit JSON diagnostics from rss run
1 parent da4b07c commit fb358d9

3 files changed

Lines changed: 76 additions & 28 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,8 @@ rss review [--json] --diff <old.rss> <new.rss>
406406
rss review [--json] --map <file-or-directory>
407407
rss lower --rust <file.rss>
408408
rss lower --rust <file.rss> --out-dir <directory>
409-
rss run <file.rss>
410-
rss run <file.rss> --out-dir <directory>
409+
rss run [--json] <file.rss>
410+
rss run [--json] <file.rss> --out-dir <directory>
411411
rss remap-rustc [--json] <rsscript-source-map.json> <rustc-json-lines>
412412
rss verify-rust [--json] <file.rss>
413413
rss verify-rust [--json] <file.rss> --out-dir <directory>
@@ -419,7 +419,7 @@ rss verify-rust [--json] <file.rss> --out-dir <directory>
419419

420420
`rss review --map` checks inputs before emitting a map. Files with frontend errors produce diagnostics instead of potentially misleading review classifications.
421421

422-
If a lowered package contains `fn main() -> Unit` or `fn main() -> Result<Unit, E>`, `rss lower --rust --out-dir` also emits a Rust `src/main.rs` harness. `rss run <file.rss>` uses the same lowering path, writes a temporary Rust package, and delegates execution to `cargo run`. Use `rss run <file.rss> --out-dir <directory>` to keep the generated Rust package for inspection.
422+
If a lowered package contains `fn main() -> Unit` or `fn main() -> Result<Unit, E>`, `rss lower --rust --out-dir` also emits a Rust `src/main.rs` harness. `rss run <file.rss>` uses the same lowering path, writes a temporary Rust package, and delegates execution to `cargo run`. Use `rss run <file.rss> --out-dir <directory>` to keep the generated Rust package for inspection. Frontend and RSScript runtime diagnostics from `rss run` support `--json`; successful program output remains the program's own stdout.
423423

424424
`rss verify-rust <file.rss> --out-dir <directory>` keeps the generated package used for backend checking, including `rsscript-source-map.json`, so unmappable rustc diagnostics can be inspected against the generated Rust.
425425

src/main.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ fn run_generated_rust(args: &[String]) -> ExitCode {
387387
) {
388388
Ok(package) => package,
389389
Err(diagnostics) => {
390-
print_diagnostics(false, &diagnostics);
390+
print_diagnostics(options.json, &diagnostics);
391391
return ExitCode::from(1);
392392
}
393393
};
@@ -441,7 +441,7 @@ fn run_generated_rust(args: &[String]) -> ExitCode {
441441
let stderr = String::from_utf8_lossy(&output.stderr);
442442
let diagnostics = parse_runtime_diagnostics(&stderr);
443443
if !diagnostics.is_empty() {
444-
print_diagnostics(false, &diagnostics);
444+
print_diagnostics(options.json, &diagnostics);
445445
return ExitCode::from(1);
446446
}
447447
if !stderr.trim().is_empty() {
@@ -563,6 +563,7 @@ struct LowerOptions<'a> {
563563
}
564564

565565
struct RunOptions<'a> {
566+
json: bool,
566567
path: Option<&'a str>,
567568
out_dir: Option<&'a str>,
568569
}
@@ -639,12 +640,15 @@ fn parse_lower_args(args: &[String]) -> LowerOptions<'_> {
639640
}
640641

641642
fn parse_run_args(args: &[String]) -> RunOptions<'_> {
643+
let mut json = false;
642644
let mut path = None;
643645
let mut out_dir = None;
644646
let mut index = 0;
645647

646648
while let Some(arg) = args.get(index) {
647-
if arg == "--out-dir" {
649+
if arg == "--json" {
650+
json = true;
651+
} else if arg == "--out-dir" {
648652
index += 1;
649653
out_dir = args.get(index).map(String::as_str);
650654
} else if path.is_none() {
@@ -653,7 +657,11 @@ fn parse_run_args(args: &[String]) -> RunOptions<'_> {
653657
index += 1;
654658
}
655659

656-
RunOptions { path, out_dir }
660+
RunOptions {
661+
json,
662+
path,
663+
out_dir,
664+
}
657665
}
658666

659667
fn parse_verify_args(args: &[String]) -> VerifyOptions<'_> {
@@ -929,8 +937,8 @@ fn print_usage() {
929937
eprintln!(" rsscript fmt <file.rss>");
930938
eprintln!(" rsscript lower --rust <file.rss>");
931939
eprintln!(" rsscript lower --rust <file.rss> --out-dir <directory>");
932-
eprintln!(" rsscript run <file.rss>");
933-
eprintln!(" rsscript run <file.rss> --out-dir <directory>");
940+
eprintln!(" rsscript run [--json] <file.rss>");
941+
eprintln!(" rsscript run [--json] <file.rss> --out-dir <directory>");
934942
eprintln!(" rsscript remap-rustc [--json] <rsscript-source-map.json> <rustc-json-lines>");
935943
eprintln!(" rsscript verify-rust [--json] <file.rss>");
936944
eprintln!(" rsscript verify-rust [--json] <file.rss> --out-dir <directory>");

tests/checker.rs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,25 +1108,7 @@ fn rss_run_maps_runtime_conflict_to_rsscript_diagnostic() {
11081108
let temp_dir = unique_temp_dir("rsscript-runtime-diagnostic");
11091109
fs::create_dir_all(&temp_dir).expect("temp dir should be created");
11101110
let source_path = temp_dir.join("empty_pool.rss");
1111-
fs::write(
1112-
&source_path,
1113-
r#"features: local
1114-
1115-
fn main() -> Unit {
1116-
local pool = ResourcePool<DbConnection>.new(
1117-
create: || DbConnection.open(url: read "db://local"),
1118-
max_size: 0,
1119-
)
1120-
1121-
with ResourcePool.borrow(pool: mut pool) as conn {
1122-
Log.write(message: read "unreachable")
1123-
}
1124-
1125-
return Unit
1126-
}
1127-
"#,
1128-
)
1129-
.expect("runtime diagnostic fixture should be written");
1111+
write_runtime_conflict_fixture(&source_path);
11301112

11311113
let output = Command::new(env!("CARGO_BIN_EXE_rss"))
11321114
.arg("run")
@@ -1152,6 +1134,42 @@ fn main() -> Unit {
11521134
);
11531135
}
11541136

1137+
#[test]
1138+
fn rss_run_json_maps_runtime_conflict_to_diagnostics_json() {
1139+
let temp_dir = unique_temp_dir("rsscript-runtime-diagnostic-json");
1140+
fs::create_dir_all(&temp_dir).expect("temp dir should be created");
1141+
let source_path = temp_dir.join("empty_pool.rss");
1142+
write_runtime_conflict_fixture(&source_path);
1143+
1144+
let output = Command::new(env!("CARGO_BIN_EXE_rss"))
1145+
.arg("run")
1146+
.arg("--json")
1147+
.arg(&source_path)
1148+
.current_dir(env!("CARGO_MANIFEST_DIR"))
1149+
.output()
1150+
.expect("rss run should execute");
1151+
let _ = fs::remove_dir_all(&temp_dir);
1152+
let stdout = String::from_utf8_lossy(&output.stdout);
1153+
let stderr = String::from_utf8_lossy(&output.stderr);
1154+
let json: Value = serde_json::from_str(&stdout).expect("stdout should be diagnostics JSON");
1155+
1156+
assert!(!output.status.success());
1157+
assert!(stderr.trim().is_empty(), "{stderr}");
1158+
assert_eq!(json[0]["code"], "RS1201");
1159+
assert_eq!(json[0]["severity"], "error");
1160+
assert_eq!(
1161+
json[0]["spans"][0]["file"],
1162+
source_path.display().to_string()
1163+
);
1164+
assert!(
1165+
json[0]["causes"]
1166+
.as_array()
1167+
.expect("causes should be an array")
1168+
.iter()
1169+
.any(|cause| cause == "runtime error kind: resource_pool_empty")
1170+
);
1171+
}
1172+
11551173
#[test]
11561174
fn rust_lowering_targets_runtime_crate_hooks() {
11571175
let source = r#"
@@ -2235,6 +2253,28 @@ fn unique_temp_dir(prefix: &str) -> PathBuf {
22352253
std::env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id()))
22362254
}
22372255

2256+
fn write_runtime_conflict_fixture(path: &Path) {
2257+
fs::write(
2258+
path,
2259+
r#"features: local
2260+
2261+
fn main() -> Unit {
2262+
local pool = ResourcePool<DbConnection>.new(
2263+
create: || DbConnection.open(url: read "db://local"),
2264+
max_size: 0,
2265+
)
2266+
2267+
with ResourcePool.borrow(pool: mut pool) as conn {
2268+
Log.write(message: read "unreachable")
2269+
}
2270+
2271+
return Unit
2272+
}
2273+
"#,
2274+
)
2275+
.expect("runtime diagnostic fixture should be written");
2276+
}
2277+
22382278
fn expected_codes(source: &str) -> Vec<String> {
22392279
let first_line = source.lines().next().unwrap_or_default();
22402280
let Some(codes) = first_line.strip_prefix("// expect:") else {

0 commit comments

Comments
 (0)