zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 7db2ef6104921c42ce42e8edda848160eda8973d (tree)
parent 56e313b288928e998e186286a2ddc3211c20aa71
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Thu, 25 Jun 2026 13:10:59 -0700

zig fmt: add simple tool for measuring complexity

can be used to share how an edit to a source file increases or reduces
complexity with a heuristic that is more insightful than line count

Diffstat:
Msrc/fmt.zig | 46++++++++++++++++++++++++++++++++--------------
1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/src/fmt.zig b/src/fmt.zig @@ -23,6 +23,7 @@ const usage_fmt = \\ --ast-check Run zig ast-check on every file \\ --exclude [file] Exclude file or directory from formatting \\ --zon Treat all input files as ZON, regardless of file extension + \\ --complexity Print a complexity report for each file as well as total \\ \\ ; @@ -39,6 +40,10 @@ const Fmt = struct { out_buffer: std.Io.Writer.Allocating, stdout_writer: *Io.File.Writer, + complexity: bool, + total_tokens: u64, + total_nodes: u64, + const SeenMap = std.AutoHashMap(Io.File.INode, void); }; @@ -48,8 +53,11 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! var check_flag = false; var check_ast_flag = false; var force_zon = false; + var complexity = false; + var input_files = std.array_list.Managed([]const u8).init(gpa); defer input_files.deinit(); + var excluded_files = std.array_list.Managed([]const u8).init(gpa); defer excluded_files.deinit(); @@ -68,7 +76,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! i += 1; const next_arg = args[i]; color = std.meta.stringToEnum(Color, next_arg) orelse { - fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); + fatal("expected [auto|on|off] after --color, found {q}", .{next_arg}); }; } else if (mem.eql(u8, arg, "--stdin")) { stdin_flag = true; @@ -76,6 +84,8 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! check_flag = true; } else if (mem.eql(u8, arg, "--ast-check")) { check_ast_flag = true; + } else if (mem.eql(u8, arg, "--complexity")) { + complexity = true; } else if (mem.eql(u8, arg, "--exclude")) { if (i + 1 >= args.len) { fatal("expected parameter after --exclude", .{}); @@ -86,7 +96,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! } else if (mem.eql(u8, arg, "--zon")) { force_zon = true; } else { - fatal("unrecognized parameter: '{s}'", .{arg}); + fatal("unrecognized parameter: {q}", .{arg}); } } else { try input_files.append(arg); @@ -175,6 +185,9 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! .color = color, .out_buffer = .init(gpa), .stdout_writer = &stdout_writer, + .complexity = complexity, + .total_tokens = 0, + .total_nodes = 0, }; defer fmt.seen.deinit(); defer fmt.out_buffer.deinit(); @@ -198,7 +211,12 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! for (input_files.items) |file_path| { try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path); } - try fmt.stdout_writer.interface.flush(); + + if (complexity) { + std.log.info("total: tokens={d} nodes={d}", .{ fmt.total_tokens, fmt.total_nodes }); + } + + try fmt.stdout_writer.flush(); if (fmt.any_error) { process.exit(1); } @@ -208,7 +226,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: Io.Dir, sub_ fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { - std.log.err("unable to format '{s}': {s}", .{ file_path, @errorName(err) }); + std.log.err("formatting {q}: {t}", .{ file_path, err }); fmt.any_error = true; return; }, @@ -244,7 +262,7 @@ fn fmtPathDir( try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); } else { fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { - std.log.err("unable to format '{s}': {t}", .{ full_path, err }); + std.log.err("unable to format {q}: {t}", .{ full_path, err }); fmt.any_error = true; return; }; @@ -341,6 +359,12 @@ fn fmtPathFile( } } + if (fmt.complexity) { + std.log.info("{s}: tokens={d} nodes={d}", .{ file_path, tree.tokens.len, tree.nodes.len }); + fmt.total_tokens += tree.tokens.len; + fmt.total_nodes += tree.nodes.len; + } + // As a heuristic, we make enough capacity for the same as the input source. fmt.out_buffer.clearRetainingCapacity(); try fmt.out_buffer.ensureTotalCapacity(source_code.len); @@ -365,13 +389,7 @@ fn fmtPathFile( } /// Provided for debugging/testing purposes; unused by the compiler. -pub fn main() !void { - const gpa = std.heap.smp_allocator; - var arena_instance = std.heap.ArenaAllocator.init(gpa); - const arena = arena_instance.allocator(); - const args = try process.argsAlloc(arena); - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - return run(gpa, arena, io, args[1..]); +pub fn main(init: process.Init) !void { + const args = try init.minimal.args.toSlice(init.arena.allocator()); + return run(init.gpa, init.arena.allocator(), init.io, args[1..]); }