Iter 4a: ail check --json mit strukturierten Diagnostics

Neue Top-Level-API check_module(&Module) -> Vec<Diagnostic> in
ailang-check, plus stabile Codes (unbound-var, type-mismatch,
arity-mismatch, non-exhaustive-match, unknown-ctor-in-pattern,
duplicate-def, …). CLI bekommt --json-Flag für maschinenlesbares
Output, Exit 1 bei Errors. Text-Modus unverändert. E2E-Test
check_json_unbound_var sichert das Format ab.
This commit is contained in:
2026-05-07 11:07:36 +02:00
parent 44243a515e
commit 93fe7237e3
7 changed files with 334 additions and 11 deletions
+31
View File
@@ -63,3 +63,34 @@ fn list_sum_via_match() {
let stdout = build_and_run("list.ail.json");
assert_eq!(stdout.trim(), "42");
}
/// Schützt das `--json`-Diagnostic-Format für Tooling-Konsumenten.
/// `broken_unbound.ail.json` referenziert eine nicht-existente Variable;
/// erwartet wird Exit-Code 1 und mindestens ein Diagnostic mit
/// `severity == "error"` und `code == "unbound-var"`.
#[test]
fn check_json_unbound_var() {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
let src = workspace.join("examples").join("broken_unbound.ail.json");
let output = Command::new(ail_bin())
.args(["check", src.to_str().unwrap(), "--json"])
.output()
.expect("ail check --json failed to run");
let code = output.status.code().expect("process terminated by signal");
assert_eq!(code, 1, "expected exit code 1, stderr: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
let diags: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("stdout must be valid JSON");
let arr = diags.as_array().expect("diagnostics must be a JSON array");
assert!(
arr.iter().any(|d| {
d.get("severity").and_then(|v| v.as_str()) == Some("error")
&& d.get("code").and_then(|v| v.as_str()) == Some("unbound-var")
}),
"expected at least one error diagnostic with code unbound-var; got: {stdout}"
);
}