2 \\Usage: zig fmt [file]...
4 \\ Formats the input files and modifies them in-place.
5 \\ Arguments can be files or directories, which are searched
9 \\ -h, --help Print this help and exit
10 \\ --color [auto|off|on] Enable or disable colored error messages
11 \\ --stdin Format code from stdin; output to stdout
12 \\ --check List non-conforming files and exit with an error
13 \\ if the list is non-empty
14 \\ --ast-check Run zig ast-check on every file
15 \\ --exclude [file] Exclude file or directory from formatting
16 \\ --zon Treat all input files as ZON, regardless of file extension
29 out_buffer: std.ArrayList(u8),
31 const SeenMap = std.AutoHashMap(fs.File.INode, void);
37 args: []const []const u8,
39 var color: Color = .auto;
40 var stdin_flag = false;
41 var check_flag = false;
42 var check_ast_flag = false;
43 var force_zon = false;
44 var input_files = std.ArrayList([]const u8).init(gpa);
45 defer input_files.deinit();
46 var excluded_files = std.ArrayList([]const u8).init(gpa);
47 defer excluded_files.deinit();
51 while (i < args.len) : (i += 1) {
53 if (mem.startsWith(u8, arg, "-")) {
54 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
55 const stdout = std.io.getStdOut().writer();
56 try stdout.writeAll(usage_fmt);
57 return process.cleanExit();
58 } else if (mem.eql(u8, arg, "--color")) {
59 if (i + 1 >= args.len) {
60 fatal("expected [auto|on|off] after --color", .{});
63 const next_arg = args[i];
64 color = std.meta.stringToEnum(Color, next_arg) orelse {
65 fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg});
67 } else if (mem.eql(u8, arg, "--stdin")) {
69 } else if (mem.eql(u8, arg, "--check")) {
71 } else if (mem.eql(u8, arg, "--ast-check")) {
72 check_ast_flag = true;
73 } else if (mem.eql(u8, arg, "--exclude")) {
74 if (i + 1 >= args.len) {
75 fatal("expected parameter after --exclude", .{});
78 const next_arg = args[i];
79 try excluded_files.append(next_arg);
80 } else if (mem.eql(u8, arg, "--zon")) {
83 fatal("unrecognized parameter: '{s}'", .{arg});
86 try input_files.append(arg);
92 if (input_files.items.len != 0) {
93 fatal("cannot use --stdin with positional arguments", .{});
96 const stdin = std.io.getStdIn();
97 const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| {
98 fatal("unable to read stdin: {}", .{err});
100 defer gpa.free(source_code);
102 var tree = std.zig.Ast.parse(gpa, source_code, if (force_zon) .zon else .zig) catch |err| {
103 fatal("error parsing stdin: {}", .{err});
105 defer tree.deinit(gpa);
107 if (check_ast_flag) {
109 var zir = try std.zig.AstGen.generate(gpa, tree);
110 defer zir.deinit(gpa);
112 if (zir.hasCompileErrors()) {
113 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
114 try wip_errors.init(gpa);
115 defer wip_errors.deinit();
116 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
117 var error_bundle = try wip_errors.toOwnedBundle("");
118 defer error_bundle.deinit(gpa);
119 error_bundle.renderToStdErr(color.renderOptions());
123 const zoir = try std.zig.ZonGen.generate(gpa, tree, .{});
124 defer zoir.deinit(gpa);
126 if (zoir.hasCompileErrors()) {
127 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
128 try wip_errors.init(gpa);
129 defer wip_errors.deinit();
130 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");
131 var error_bundle = try wip_errors.toOwnedBundle("");
132 defer error_bundle.deinit(gpa);
133 error_bundle.renderToStdErr(color.renderOptions());
137 } else if (tree.errors.len != 0) {
138 try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color);
141 const formatted = try tree.render(gpa);
142 defer gpa.free(formatted);
145 const code: u8 = @intFromBool(mem.eql(u8, formatted, source_code));
149 return std.io.getStdOut().writeAll(formatted);
152 if (input_files.items.len == 0) {
153 fatal("expected at least one source file argument", .{});
161 .check_ast = check_ast_flag,
162 .force_zon = force_zon,
164 .out_buffer = std.ArrayList(u8).init(gpa),
166 defer fmt.seen.deinit();
167 defer fmt.out_buffer.deinit();
169 // Mark any excluded files/directories as already seen,
170 // so that they are skipped later during actual processing
171 for (excluded_files.items) |file_path| {
172 const stat = fs.cwd().statFile(file_path) catch |err| switch (err) {
173 error.FileNotFound => continue,
174 // On Windows, statFile does not work for directories
175 error.IsDir => dir: {
176 var dir = try fs.cwd().openDir(file_path, .{});
178 break :dir try dir.stat();
180 else => |e| return e,
182 try fmt.seen.put(stat.inode, {});
185 for (input_files.items) |file_path| {
186 try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path);
193 const FmtError = error{
202 DestinationAddressRequired,
209 RenameAcrossMountPoints,
217 ConnectionResetByPeer,
223 } || fs.File.OpenError;
225 fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void {
226 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
227 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
229 std.log.err("unable to format '{s}': {s}", .{ file_path, @errorName(err) });
230 fmt.any_error = true;
238 file_path: []const u8,
241 parent_sub_path: []const u8,
243 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
246 const stat = try dir.stat();
247 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
249 var dir_it = dir.iterate();
250 while (try dir_it.next()) |entry| {
251 const is_dir = entry.kind == .directory;
253 if (mem.startsWith(u8, entry.name, ".")) continue;
255 if (is_dir or entry.kind == .file and (mem.endsWith(u8, entry.name, ".zig") or mem.endsWith(u8, entry.name, ".zon"))) {
256 const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
257 defer fmt.gpa.free(full_path);
260 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
262 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
263 std.log.err("unable to format '{s}': {s}", .{ full_path, @errorName(err) });
264 fmt.any_error = true;
274 file_path: []const u8,
277 sub_path: []const u8,
279 const source_file = try dir.openFile(sub_path, .{});
280 var file_closed = false;
281 errdefer if (!file_closed) source_file.close();
283 const stat = try source_file.stat();
285 if (stat.kind == .directory)
289 const source_code = try std.zig.readSourceFileToEndAlloc(
292 std.math.cast(usize, stat.size) orelse return error.FileTooBig,
294 defer gpa.free(source_code);
299 // Add to set after no longer possible to get error.IsDir.
300 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
302 const mode: std.zig.Ast.Mode = mode: {
303 if (fmt.force_zon) break :mode .zon;
304 if (mem.endsWith(u8, sub_path, ".zon")) break :mode .zon;
308 var tree = try std.zig.Ast.parse(gpa, source_code, mode);
309 defer tree.deinit(gpa);
311 if (tree.errors.len != 0) {
312 try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color);
313 fmt.any_error = true;
318 if (stat.size > std.zig.max_src_size)
319 return error.FileTooBig;
323 var zir = try std.zig.AstGen.generate(gpa, tree);
324 defer zir.deinit(gpa);
326 if (zir.hasCompileErrors()) {
327 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
328 try wip_errors.init(gpa);
329 defer wip_errors.deinit();
330 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
331 var error_bundle = try wip_errors.toOwnedBundle("");
332 defer error_bundle.deinit(gpa);
333 error_bundle.renderToStdErr(fmt.color.renderOptions());
334 fmt.any_error = true;
338 var zoir = try std.zig.ZonGen.generate(gpa, tree, .{});
339 defer zoir.deinit(gpa);
341 if (zoir.hasCompileErrors()) {
342 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
343 try wip_errors.init(gpa);
344 defer wip_errors.deinit();
345 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);
346 var error_bundle = try wip_errors.toOwnedBundle("");
347 defer error_bundle.deinit(gpa);
348 error_bundle.renderToStdErr(fmt.color.renderOptions());
349 fmt.any_error = true;
355 // As a heuristic, we make enough capacity for the same as the input source.
356 fmt.out_buffer.shrinkRetainingCapacity(0);
357 try fmt.out_buffer.ensureTotalCapacity(source_code.len);
359 try tree.renderToArrayList(&fmt.out_buffer, .{});
360 if (mem.eql(u8, fmt.out_buffer.items, source_code))
364 const stdout = std.io.getStdOut().writer();
365 try stdout.print("{s}\n", .{file_path});
366 fmt.any_error = true;
368 var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode });
371 try af.file.writeAll(fmt.out_buffer.items);
373 const stdout = std.io.getStdOut().writer();
374 try stdout.print("{s}\n", .{file_path});
378 const std = @import("std");
381 const process = std.process;
382 const Allocator = std.mem.Allocator;
383 const Color = std.zig.Color;
384 const fatal = std.process.fatal;