lang F0.3: multi-message diagnostic bundling + help-blocks

Feature 0 complete. addNote/addHelp bundle notes and help-blocks under a
primary diagnostic (handle from new addId/addFmtId); help blocks carry an
optional fix-it line that substitutes the suggested source. renderExtended
now renders primary -> notes -> helps with blank-line separators.

Wire the CLI to the extended renderer (renderErrors -> renderStderr) and
flip render_style default to .extended; the previous renderErrors ->
renderDebug path bypassed render() entirely, so flipping the field alone
was a no-op. 13 diagnostic snapshots re-rendered to the extended format.
This commit is contained in:
agra
2026-05-29 09:36:53 +03:00
parent cc08f9a9fe
commit 9bf3dc75e6
17 changed files with 328 additions and 44 deletions

View File

@@ -237,12 +237,116 @@ test "renderExtended: multi-line span renders each line with carets" {
try testing.expectEqualStrings(expected, output);
}
test "render: default compact style still works" {
test "renderExtended: primary bundled with one note" {
var dl = errors.DiagnosticList.init(testing.allocator, "let x = y\nlet y = z\n", "b.sx");
defer dl.deinit();
const p = dl.addId(.err, "undefined name", Span{ .start = 8, .end = 9 });
dl.addNote(p, Span{ .start = 14, .end = 15 }, "declared here");
const output = try renderToString(&dl, testing.allocator);
defer testing.allocator.free(output);
const expected =
\\error: undefined name
\\ --> b.sx:1:9
\\ |
\\ 1 | let x = y
\\ | ^
\\
\\note: declared here
\\ --> b.sx:2:5
\\ |
\\ 2 | let y = z
\\ | ^
\\
;
try testing.expectEqualStrings(expected, output);
}
test "renderExtended: primary bundled with help (no fix code)" {
var dl = errors.DiagnosticList.init(testing.allocator, "foo bar\n", "h.sx");
defer dl.deinit();
const p = dl.addId(.err, "bad thing", Span{ .start = 0, .end = 3 });
dl.addHelp(p, null, "try something else", null);
const output = try renderToString(&dl, testing.allocator);
defer testing.allocator.free(output);
const expected =
\\error: bad thing
\\ --> h.sx:1:1
\\ |
\\ 1 | foo bar
\\ | ^^^
\\
\\help: try something else
\\
;
try testing.expectEqualStrings(expected, output);
}
test "renderExtended: help with fix-it code substitutes the line and omits arrow" {
var dl = errors.DiagnosticList.init(testing.allocator, " x := foo.value\n", "f.sx");
defer dl.deinit();
// primary span covers "value" at columns 12..17.
const p = dl.addId(.err, "no such field", Span{ .start = 11, .end = 16 });
dl.addHelp(p, Span{ .start = 11, .end = 16 }, "did you mean `val`?", " x := foo.val");
const output = try renderToString(&dl, testing.allocator);
defer testing.allocator.free(output);
const expected =
\\error: no such field
\\ --> f.sx:1:12
\\ |
\\ 1 | x := foo.value
\\ | ^^^^^
\\
\\help: did you mean `val`?
\\ |
\\ 1 | x := foo.val
\\ | ^^^^^
\\
;
try testing.expectEqualStrings(expected, output);
}
test "renderExtended: note and help bundle in note-then-help order" {
var dl = errors.DiagnosticList.init(testing.allocator, "aaa\n", "o.sx");
defer dl.deinit();
const p = dl.addId(.err, "primary", Span{ .start = 0, .end = 3 });
// Add help first, then note: rendering must still emit note before help.
dl.addHelp(p, null, "the help", null);
dl.addNote(p, Span{ .start = 0, .end = 3 }, "the note");
const output = try renderToString(&dl, testing.allocator);
defer testing.allocator.free(output);
const expected =
\\error: primary
\\ --> o.sx:1:1
\\ |
\\ 1 | aaa
\\ | ^^^
\\
\\note: the note
\\ --> o.sx:1:1
\\ |
\\ 1 | aaa
\\ | ^^^
\\
\\help: the help
\\
;
try testing.expectEqualStrings(expected, output);
}
test "render: compact style still available behind the flag" {
var dl = errors.DiagnosticList.init(testing.allocator, "abc\n", "c.sx");
defer dl.deinit();
dl.render_style = .compact;
dl.add(.err, "compact error", Span{ .start = 0, .end = 3 });
// Default render_style is .compact — preserves the existing gcc-style.
var aw = std.Io.Writer.Allocating.init(testing.allocator);
defer {
var r = aw.writer.toArrayList();