authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-22 17:03:52-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-02-22 17:03:52-05:00
log0dcba03b67af0aee612b51191a8110d91a55019e
treee4aaeaa26ce2a306de343dde151ba34eb4083474
parent31f353cd925a565dc32fa013c3ff767a06963adc
parent75ccdcc356839687033ba982a34fe0703ba1e349
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #22902 from ianprime0509/autodoc-error-reporting

Autodoc: improve error reporting

4 files changed, 100 insertions(+), 36 deletions(-)

lib/docs/index.html+32
...@@ -6,6 +6,9 @@...@@ -6,6 +6,9 @@
6 <title>Zig Documentation</title>6 <title>Zig Documentation</title>
7 <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTMgMTQwIj48ZyBmaWxsPSIjRjdBNDFEIj48Zz48cG9seWdvbiBwb2ludHM9IjQ2LDIyIDI4LDQ0IDE5LDMwIi8+PHBvbHlnb24gcG9pbnRzPSI0NiwyMiAzMywzMyAyOCw0NCAyMiw0NCAyMiw5NSAzMSw5NSAyMCwxMDAgMTIsMTE3IDAsMTE3IDAsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMzEsOTUgMTIsMTE3IDQsMTA2Ii8+PC9nPjxnPjxwb2x5Z29uIHBvaW50cz0iNTYsMjIgNjIsMzYgMzcsNDQiLz48cG9seWdvbiBwb2ludHM9IjU2LDIyIDExMSwyMiAxMTEsNDQgMzcsNDQgNTYsMzIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTE2LDk1IDk3LDExNyA5MCwxMDQiLz48cG9seWdvbiBwb2ludHM9IjExNiw5NSAxMDAsMTA0IDk3LDExNyA0MiwxMTcgNDIsOTUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTUwLDAgNTIsMTE3IDMsMTQwIDEwMSwyMiIvPjwvZz48Zz48cG9seWdvbiBwb2ludHM9IjE0MSwyMiAxNDAsNDAgMTIyLDQ1Ii8+PHBvbHlnb24gcG9pbnRzPSIxNTMsMjIgMTUzLDExNyAxMDYsMTE3IDEyMCwxMDUgMTI1LDk1IDEzMSw5NSAxMzEsNDUgMTIyLDQ1IDEzMiwzNiAxNDEsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTI1LDk1IDEzMCwxMTAgMTA2LDExNyIvPjwvZz48L2c+PC9zdmc+">7 <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTMgMTQwIj48ZyBmaWxsPSIjRjdBNDFEIj48Zz48cG9seWdvbiBwb2ludHM9IjQ2LDIyIDI4LDQ0IDE5LDMwIi8+PHBvbHlnb24gcG9pbnRzPSI0NiwyMiAzMywzMyAyOCw0NCAyMiw0NCAyMiw5NSAzMSw5NSAyMCwxMDAgMTIsMTE3IDAsMTE3IDAsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMzEsOTUgMTIsMTE3IDQsMTA2Ii8+PC9nPjxnPjxwb2x5Z29uIHBvaW50cz0iNTYsMjIgNjIsMzYgMzcsNDQiLz48cG9seWdvbiBwb2ludHM9IjU2LDIyIDExMSwyMiAxMTEsNDQgMzcsNDQgNTYsMzIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTE2LDk1IDk3LDExNyA5MCwxMDQiLz48cG9seWdvbiBwb2ludHM9IjExNiw5NSAxMDAsMTA0IDk3LDExNyA0MiwxMTcgNDIsOTUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTUwLDAgNTIsMTE3IDMsMTQwIDEwMSwyMiIvPjwvZz48Zz48cG9seWdvbiBwb2ludHM9IjE0MSwyMiAxNDAsNDAgMTIyLDQ1Ii8+PHBvbHlnb24gcG9pbnRzPSIxNTMsMjIgMTUzLDExNyAxMDYsMTE3IDEyMCwxMDUgMTI1LDk1IDEzMSw5NSAxMzEsNDUgMTIyLDQ1IDEzMiwzNiAxNDEsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTI1LDk1IDEzMCwxMTAgMTA2LDExNyIvPjwvZz48L2c+PC9zdmc+">
8 <style type="text/css">8 <style type="text/css">
9 *, *::before, *::after {
10 box-sizing: border-box;
11 }
9 body {12 body {
10 font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;13 font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
11 color: #000000;14 color: #000000;
...@@ -157,6 +160,23 @@...@@ -157,6 +160,23 @@
157 cursor: default;160 cursor: default;
158 }161 }
159162
163 #errors {
164 background-color: #faa;
165 position: fixed;
166 left: 0;
167 bottom: 0;
168 width: 100%;
169 max-height: min(20em, 50vh);
170 padding: 0.5em;
171 overflow: auto;
172 }
173 #errors h1 {
174 font-size: 1.5em;
175 }
176 #errors pre {
177 background-color: #fcc;
178 }
179
160 #listSearchResults li.selected {180 #listSearchResults li.selected {
161 background-color: #93e196;181 background-color: #93e196;
162 }182 }
...@@ -252,6 +272,14 @@...@@ -252,6 +272,14 @@
252 #listSearchResults li.selected a {272 #listSearchResults li.selected a {
253 color: #fff;273 color: #fff;
254 }274 }
275 #errors {
276 background-color: #800;
277 color: #fff;
278 }
279 #errors pre {
280 background-color: #a00;
281 color: #fff;
282 }
255 dl > div {283 dl > div {
256 border-color: #373737;284 border-color: #373737;
257 }285 }
...@@ -414,6 +442,10 @@...@@ -414,6 +442,10 @@
414 <dl><dt><kbd>↓</kbd></dt><dd>Move down in search results</dd></dl>442 <dl><dt><kbd>↓</kbd></dt><dd>Move down in search results</dd></dl>
415 <dl><dt><kbd>⏎</kbd></dt><dd>Go to active search result</dd></dl>443 <dl><dt><kbd>⏎</kbd></dt><dd>Go to active search result</dd></dl>
416 </div>444 </div>
445 <div id="errors" class="hidden">
446 <h1>Errors</h1>
447 <pre id="errorsText"></pre>
448 </div>
417 <script src="main.js"></script>449 <script src="main.js"></script>
418 </body>450 </body>
419</html>451</html>
lib/docs/main.js+24-6
...@@ -11,6 +11,11 @@...@@ -11,6 +11,11 @@
11 const CAT_type_type = 9;11 const CAT_type_type = 9;
12 const CAT_type_function = 10;12 const CAT_type_function = 10;
1313
14 const LOG_err = 0;
15 const LOG_warn = 1;
16 const LOG_info = 2;
17 const LOG_debug = 3;
18
14 const domDocTestsCode = document.getElementById("docTestsCode");19 const domDocTestsCode = document.getElementById("docTestsCode");
15 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");20 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
16 const domFnProto = document.getElementById("fnProto");21 const domFnProto = document.getElementById("fnProto");
...@@ -48,6 +53,8 @@...@@ -48,6 +53,8 @@
48 const domStatus = document.getElementById("status");53 const domStatus = document.getElementById("status");
49 const domTableFnErrors = document.getElementById("tableFnErrors");54 const domTableFnErrors = document.getElementById("tableFnErrors");
50 const domTldDocs = document.getElementById("tldDocs");55 const domTldDocs = document.getElementById("tldDocs");
56 const domErrors = document.getElementById("errors");
57 const domErrorsText = document.getElementById("errorsText");
5158
52 var searchTimer = null;59 var searchTimer = null;
5360
...@@ -84,13 +91,24 @@...@@ -84,13 +91,24 @@
8491
85 WebAssembly.instantiateStreaming(wasm_promise, {92 WebAssembly.instantiateStreaming(wasm_promise, {
86 js: {93 js: {
87 log: function(ptr, len) {94 log: function(level, ptr, len) {
88 const msg = decodeString(ptr, len);95 const msg = decodeString(ptr, len);
89 console.log(msg);96 switch (level) {
90 },97 case LOG_err:
91 panic: function (ptr, len) {98 console.error(msg);
92 const msg = decodeString(ptr, len);99 domErrorsText.textContent += msg + "\n";
93 throw new Error("panic: " + msg);100 domErrors.classList.remove("hidden");
101 break;
102 case LOG_warn:
103 console.warn(msg);
104 break;
105 case LOG_info:
106 console.info(msg);
107 break;
108 case LOG_debug:
109 console.debug(msg);
110 break;
111 }
94 },112 },
95 },113 },
96 }).then(function(obj) {114 }).then(function(obj) {
lib/docs/wasm/Walk.zig+27-10
...@@ -406,15 +406,11 @@ pub const ModuleIndex = enum(u32) {...@@ -406,15 +406,11 @@ pub const ModuleIndex = enum(u32) {
406};406};
407407
408pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {408pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {
409 const ast = try parse(bytes);409 const ast = try parse(file_name, bytes);
410 assert(ast.errors.len == 0);
410 const file_index: File.Index = @enumFromInt(files.entries.len);411 const file_index: File.Index = @enumFromInt(files.entries.len);
411 try files.put(gpa, file_name, .{ .ast = ast });412 try files.put(gpa, file_name, .{ .ast = ast });
412413
413 if (ast.errors.len > 0) {
414 log.err("can't index '{s}' because it has syntax errors", .{file_index.path()});
415 return file_index;
416 }
417
418 var w: Walk = .{414 var w: Walk = .{
419 .file = file_index,415 .file = file_index,
420 };416 };
...@@ -434,20 +430,41 @@ pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {...@@ -434,20 +430,41 @@ pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {
434 return file_index;430 return file_index;
435}431}
436432
437fn parse(source: []u8) Oom!Ast {433/// Parses a file and returns its `Ast`. If the file cannot be parsed, returns
434/// the `Ast` of an empty file, so that the rest of the Autodoc logic does not
435/// need to handle parse errors.
436fn parse(file_name: []const u8, source: []u8) Oom!Ast {
438 // Require every source file to end with a newline so that Zig's tokenizer437 // Require every source file to end with a newline so that Zig's tokenizer
439 // can continue to require null termination and Autodoc implementation can438 // can continue to require null termination and Autodoc implementation can
440 // avoid copying source bytes from the decompressed tar file buffer.439 // avoid copying source bytes from the decompressed tar file buffer.
441 const adjusted_source: [:0]const u8 = s: {440 const adjusted_source: [:0]const u8 = s: {
442 if (source.len == 0)441 if (source.len == 0)
443 break :s "";442 break :s "";
444443 if (source[source.len - 1] != '\n') {
445 assert(source[source.len - 1] == '\n');444 log.err("{s}: expected newline at end of file", .{file_name});
445 break :s "";
446 }
446 source[source.len - 1] = 0;447 source[source.len - 1] = 0;
447 break :s source[0 .. source.len - 1 :0];448 break :s source[0 .. source.len - 1 :0];
448 };449 };
449450
450 return Ast.parse(gpa, adjusted_source, .zig);451 var ast = try Ast.parse(gpa, adjusted_source, .zig);
452 if (ast.errors.len > 0) {
453 defer ast.deinit(gpa);
454
455 const token_offsets = ast.tokens.items(.start);
456 var rendered_err: std.ArrayListUnmanaged(u8) = .{};
457 defer rendered_err.deinit(gpa);
458 for (ast.errors) |err| {
459 const err_offset = token_offsets[err.token] + ast.errorOffset(err);
460 const err_loc = std.zig.findLineColumn(ast.source, err_offset);
461 rendered_err.clearRetainingCapacity();
462 try ast.renderError(err, rendered_err.writer(gpa));
463 log.err("{s}:{}:{}: {s}", .{ file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.items });
464 }
465 return Ast.parse(gpa, "", .zig);
466 }
467 return ast;
451}468}
452469
453pub const Scope = struct {470pub const Scope = struct {
lib/docs/wasm/main.zig+17-20
...@@ -14,8 +14,15 @@ const missing_feature_url_escape = @import("html_render.zig").missing_feature_ur...@@ -14,8 +14,15 @@ const missing_feature_url_escape = @import("html_render.zig").missing_feature_ur
14const gpa = std.heap.wasm_allocator;14const gpa = std.heap.wasm_allocator;
1515
16const js = struct {16const js = struct {
17 extern "js" fn log(ptr: [*]const u8, len: usize) void;17 /// Keep in sync with the `LOG_` constants in `main.js`.
18 extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;18 const LogLevel = enum(u8) {
19 err,
20 warn,
21 info,
22 debug,
23 };
24
25 extern "js" fn log(level: LogLevel, ptr: [*]const u8, len: usize) void;
19};26};
2027
21pub const std_options: std.Options = .{28pub const std_options: std.Options = .{
...@@ -36,14 +43,13 @@ fn logFn(...@@ -36,14 +43,13 @@ fn logFn(
36 comptime format: []const u8,43 comptime format: []const u8,
37 args: anytype,44 args: anytype,
38) void {45) void {
39 const level_txt = comptime message_level.asText();46 const prefix = if (scope == .default) "" else @tagName(scope) ++ ": ";
40 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
41 var buf: [500]u8 = undefined;47 var buf: [500]u8 = undefined;
42 const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: {48 const line = std.fmt.bufPrint(&buf, prefix ++ format, args) catch l: {
43 buf[buf.len - 3 ..][0..3].* = "...".*;49 buf[buf.len - 3 ..][0..3].* = "...".*;
44 break :l &buf;50 break :l &buf;
45 };51 };
46 js.log(line.ptr, line.len);52 js.log(@field(js.LogLevel, @tagName(message_level)), line.ptr, line.len);
47}53}
4854
49export fn alloc(n: usize) [*]u8 {55export fn alloc(n: usize) [*]u8 {
...@@ -56,7 +62,7 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void {...@@ -56,7 +62,7 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void {
56 //log.debug("received {d} bytes of tar file", .{tar_bytes.len});62 //log.debug("received {d} bytes of tar file", .{tar_bytes.len});
5763
58 unpackInner(tar_bytes) catch |err| {64 unpackInner(tar_bytes) catch |err| {
59 fatal("unable to unpack tar: {s}", .{@errorName(err)});65 std.debug.panic("unable to unpack tar: {s}", .{@errorName(err)});
60 };66 };
61}67}
6268
...@@ -514,7 +520,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri...@@ -514,7 +520,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri
514 .collapse_whitespace = true,520 .collapse_whitespace = true,
515 .fn_link = if (linkify_fn_name) decl_index else .none,521 .fn_link = if (linkify_fn_name) decl_index else .none,
516 }) catch |err| {522 }) catch |err| {
517 fatal("unable to render source: {s}", .{@errorName(err)});523 std.debug.panic("unable to render source: {s}", .{@errorName(err)});
518 };524 };
519 return String.init(string_result.items);525 return String.init(string_result.items);
520}526}
...@@ -524,7 +530,7 @@ export fn decl_source_html(decl_index: Decl.Index) String {...@@ -524,7 +530,7 @@ export fn decl_source_html(decl_index: Decl.Index) String {
524530
525 string_result.clearRetainingCapacity();531 string_result.clearRetainingCapacity();
526 fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| {532 fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| {
527 fatal("unable to render source: {s}", .{@errorName(err)});533 std.debug.panic("unable to render source: {s}", .{@errorName(err)});
528 };534 };
529 return String.init(string_result.items);535 return String.init(string_result.items);
530}536}
...@@ -536,7 +542,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {...@@ -536,7 +542,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {
536542
537 string_result.clearRetainingCapacity();543 string_result.clearRetainingCapacity();
538 fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| {544 fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| {
539 fatal("unable to render source: {s}", .{@errorName(err)});545 std.debug.panic("unable to render source: {s}", .{@errorName(err)});
540 };546 };
541 return String.init(string_result.items);547 return String.init(string_result.items);
542}548}
...@@ -740,7 +746,7 @@ export fn decl_type_html(decl_index: Decl.Index) String {...@@ -740,7 +746,7 @@ export fn decl_type_html(decl_index: Decl.Index) String {
740 .skip_comments = true,746 .skip_comments = true,
741 .collapse_whitespace = true,747 .collapse_whitespace = true,
742 }) catch |e| {748 }) catch |e| {
743 fatal("unable to render html: {s}", .{@errorName(e)});749 std.debug.panic("unable to render html: {s}", .{@errorName(e)});
744 };750 };
745 string_result.appendSlice(gpa, "</code>") catch @panic("OOM");751 string_result.appendSlice(gpa, "</code>") catch @panic("OOM");
746 break :t;752 break :t;
...@@ -791,15 +797,6 @@ fn unpackInner(tar_bytes: []u8) !void {...@@ -791,15 +797,6 @@ fn unpackInner(tar_bytes: []u8) !void {
791 }797 }
792}798}
793799
794fn fatal(comptime format: []const u8, args: anytype) noreturn {
795 var buf: [500]u8 = undefined;
796 const line = std.fmt.bufPrint(&buf, format, args) catch l: {
797 buf[buf.len - 3 ..][0..3].* = "...".*;
798 break :l &buf;
799 };
800 js.panic(line.ptr, line.len);
801}
802
803fn ascii_lower(bytes: []u8) void {800fn ascii_lower(bytes: []u8) void {
804 for (bytes) |*b| b.* = std.ascii.toLower(b.*);801 for (bytes) |*b| b.* = std.ascii.toLower(b.*);
805}802}