From 293603f04029e2b1edf235a80417f3bb9651482f Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Sat, 15 Feb 2025 15:59:32 -0500 Subject: [PATCH 1/3] Autodoc: report syntax errors to user Additionally, this commit streamlines the way unparseable files are handled, by giving them the AST of an empty file. This avoids bugs in the rest of the Autodoc logic trying to work with invalid ASTs. --- lib/docs/wasm/Walk.zig | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/docs/wasm/Walk.zig b/lib/docs/wasm/Walk.zig index 49a5e738fc..277cf88bef 100644 --- a/lib/docs/wasm/Walk.zig +++ b/lib/docs/wasm/Walk.zig @@ -406,15 +406,11 @@ pub const ModuleIndex = enum(u32) { }; pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index { - const ast = try parse(bytes); + const ast = try parse(file_name, bytes); + assert(ast.errors.len == 0); const file_index: File.Index = @enumFromInt(files.entries.len); try files.put(gpa, file_name, .{ .ast = ast }); - if (ast.errors.len > 0) { - log.err("can't index '{s}' because it has syntax errors", .{file_index.path()}); - return file_index; - } - var w: Walk = .{ .file = file_index, }; @@ -434,20 +430,41 @@ pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index { return file_index; } -fn parse(source: []u8) Oom!Ast { +/// Parses a file and returns its `Ast`. If the file cannot be parsed, returns +/// the `Ast` of an empty file, so that the rest of the Autodoc logic does not +/// need to handle parse errors. +fn parse(file_name: []const u8, source: []u8) Oom!Ast { // Require every source file to end with a newline so that Zig's tokenizer // can continue to require null termination and Autodoc implementation can // avoid copying source bytes from the decompressed tar file buffer. const adjusted_source: [:0]const u8 = s: { if (source.len == 0) break :s ""; - - assert(source[source.len - 1] == '\n'); + if (source[source.len - 1] != '\n') { + log.err("{s}: expected newline at end of file", .{file_name}); + break :s ""; + } source[source.len - 1] = 0; break :s source[0 .. source.len - 1 :0]; }; - return Ast.parse(gpa, adjusted_source, .zig); + var ast = try Ast.parse(gpa, adjusted_source, .zig); + if (ast.errors.len > 0) { + defer ast.deinit(gpa); + + const token_offsets = ast.tokens.items(.start); + var rendered_err: std.ArrayListUnmanaged(u8) = .{}; + defer rendered_err.deinit(gpa); + for (ast.errors) |err| { + const err_offset = token_offsets[err.token] + ast.errorOffset(err); + const err_loc = std.zig.findLineColumn(ast.source, err_offset); + rendered_err.clearRetainingCapacity(); + try ast.renderError(err, rendered_err.writer(gpa)); + log.err("{s}:{}:{}: {s}", .{ file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.items }); + } + return Ast.parse(gpa, "", .zig); + } + return ast; } pub const Scope = struct { From b745a96d20e8159a68be53478d30c72f8c216dd3 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Sat, 15 Feb 2025 16:23:33 -0500 Subject: [PATCH 2/3] Autodoc: use browser console log levels and simplify panic Using the browser's `console.error`, etc. functions instead of `console.log` produces prettier output in the console. Additionally, `console.error` in particular includes a stack trace, which is useful for debugging where the error occurred. Additionally, this commit leverages the enhanced logging to delete the separate `panic` function from the JS code and write it in Zig instead. --- lib/docs/main.js | 26 ++++++++++++++++++++------ lib/docs/wasm/main.zig | 37 +++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 49c277b9f5..c4604172a9 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -11,6 +11,11 @@ const CAT_type_type = 9; const CAT_type_function = 10; + const LOG_err = 0; + const LOG_warn = 1; + const LOG_info = 2; + const LOG_debug = 3; + const domDocTestsCode = document.getElementById("docTestsCode"); const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError"); const domFnProto = document.getElementById("fnProto"); @@ -84,13 +89,22 @@ WebAssembly.instantiateStreaming(wasm_promise, { js: { - log: function(ptr, len) { + log: function(level, ptr, len) { const msg = decodeString(ptr, len); - console.log(msg); - }, - panic: function (ptr, len) { - const msg = decodeString(ptr, len); - throw new Error("panic: " + msg); + switch (level) { + case LOG_err: + console.error(msg); + break; + case LOG_warn: + console.warn(msg); + break; + case LOG_info: + console.info(msg); + break; + case LOG_debug: + console.debug(msg); + break; + } }, }, }).then(function(obj) { diff --git a/lib/docs/wasm/main.zig b/lib/docs/wasm/main.zig index 0ec2227512..91594e24b6 100644 --- a/lib/docs/wasm/main.zig +++ b/lib/docs/wasm/main.zig @@ -14,8 +14,15 @@ const missing_feature_url_escape = @import("html_render.zig").missing_feature_ur const gpa = std.heap.wasm_allocator; const js = struct { - extern "js" fn log(ptr: [*]const u8, len: usize) void; - extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn; + /// Keep in sync with the `LOG_` constants in `main.js`. + const LogLevel = enum(u8) { + err, + warn, + info, + debug, + }; + + extern "js" fn log(level: LogLevel, ptr: [*]const u8, len: usize) void; }; pub const std_options: std.Options = .{ @@ -36,14 +43,13 @@ fn logFn( comptime format: []const u8, args: anytype, ) void { - const level_txt = comptime message_level.asText(); - const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; + const prefix = if (scope == .default) "" else @tagName(scope) ++ ": "; var buf: [500]u8 = undefined; - const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: { + const line = std.fmt.bufPrint(&buf, prefix ++ format, args) catch l: { buf[buf.len - 3 ..][0..3].* = "...".*; break :l &buf; }; - js.log(line.ptr, line.len); + js.log(@field(js.LogLevel, @tagName(message_level)), line.ptr, line.len); } export fn alloc(n: usize) [*]u8 { @@ -56,7 +62,7 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { //log.debug("received {d} bytes of tar file", .{tar_bytes.len}); unpackInner(tar_bytes) catch |err| { - fatal("unable to unpack tar: {s}", .{@errorName(err)}); + std.debug.panic("unable to unpack tar: {s}", .{@errorName(err)}); }; } @@ -514,7 +520,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri .collapse_whitespace = true, .fn_link = if (linkify_fn_name) decl_index else .none, }) catch |err| { - fatal("unable to render source: {s}", .{@errorName(err)}); + std.debug.panic("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); } @@ -524,7 +530,7 @@ export fn decl_source_html(decl_index: Decl.Index) String { string_result.clearRetainingCapacity(); fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { - fatal("unable to render source: {s}", .{@errorName(err)}); + std.debug.panic("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); } @@ -536,7 +542,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String { string_result.clearRetainingCapacity(); fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { - fatal("unable to render source: {s}", .{@errorName(err)}); + std.debug.panic("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); } @@ -740,7 +746,7 @@ export fn decl_type_html(decl_index: Decl.Index) String { .skip_comments = true, .collapse_whitespace = true, }) catch |e| { - fatal("unable to render html: {s}", .{@errorName(e)}); + std.debug.panic("unable to render html: {s}", .{@errorName(e)}); }; string_result.appendSlice(gpa, "") catch @panic("OOM"); break :t; @@ -791,15 +797,6 @@ fn unpackInner(tar_bytes: []u8) !void { } } -fn fatal(comptime format: []const u8, args: anytype) noreturn { - var buf: [500]u8 = undefined; - const line = std.fmt.bufPrint(&buf, format, args) catch l: { - buf[buf.len - 3 ..][0..3].* = "...".*; - break :l &buf; - }; - js.panic(line.ptr, line.len); -} - fn ascii_lower(bytes: []u8) void { for (bytes) |*b| b.* = std.ascii.toLower(b.*); } From 75ccdcc356839687033ba982a34fe0703ba1e349 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Sat, 15 Feb 2025 16:57:04 -0500 Subject: [PATCH 3/3] Autodoc: report errors in user interface Also includes panics by virtue of the previous commit, checking one item off #19249. --- lib/docs/index.html | 32 ++++++++++++++++++++++++++++++++ lib/docs/main.js | 4 ++++ 2 files changed, 36 insertions(+) diff --git a/lib/docs/index.html b/lib/docs/index.html index b10024a3e1..e60a3f960a 100644 --- a/lib/docs/index.html +++ b/lib/docs/index.html @@ -6,6 +6,9 @@ Zig Documentation