authorgravatar for ian@ianjohnson.devIan Johnson <ian@ianjohnson.dev> 2025-02-15 15:59:32-05:00
committergravatar for ian@ianjohnson.devIan Johnson <ian@ianjohnson.dev> 2025-02-15 17:29:31-05:00
log293603f04029e2b1edf235a80417f3bb9651482f
tree9bb0c7a86fc06915823ebcd7366a00231b95d2ba
parente5174c7441e42a1ecc4d31ed505301f6d2c26d50

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.

1 files changed, 27 insertions(+), 10 deletions(-)

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 {