Commit 895fb2bd6d

Isaac Freund <ifreund@ifreund.xyz>
2021-02-16 20:57:18
zig fmt: implement 'zig fmt: (on|off)' directives
With the new implementation, these now work anywhere in the source code as opposed to only at the top level.
1 parent 070e548
Changed files (2)
lib/std/zig/parser_test.zig
@@ -878,17 +878,17 @@ test "zig fmt: async function" {
     );
 }
 
-//test "zig fmt: whitespace fixes" {
-//    try testTransform("test \"\" {\r\n\tconst hi = x;\r\n}\n// zig fmt: off\ntest \"\"{\r\n\tconst a  = b;}\r\n",
-//        \\test "" {
-//        \\    const hi = x;
-//        \\}
-//        \\// zig fmt: off
-//        \\test ""{
-//        \\    const a  = b;}
-//        \\
-//    );
-//}
+test "zig fmt: whitespace fixes" {
+    try testTransform("test \"\" {\r\n\tconst hi = x;\r\n}\n// zig fmt: off\ntest \"\"{\r\n\tconst a  = b;}\r\n",
+        \\test "" {
+        \\    const hi = x;
+        \\}
+        \\// zig fmt: off
+        \\test ""{
+        \\    const a  = b;}
+        \\
+    );
+}
 
 test "zig fmt: while else err prong with no block" {
     try testCanonical(
@@ -1098,128 +1098,154 @@ test "zig fmt: aligned struct field" {
     );
 }
 
-//test "zig fmt: comment to disable/enable zig fmt first" {
-//    try testCanonical(
-//        \\// Test trailing comma syntax
-//        \\// zig fmt: off
-//        \\
-//        \\const struct_trailing_comma = struct { x: i32, y: i32, };
-//    );
-//}
-//
-//test "zig fmt: comment to disable/enable zig fmt" {
-//    try testTransform(
-//        \\const  a  =  b;
-//        \\// zig fmt: off
-//        \\const  c  =  d;
-//        \\// zig fmt: on
-//        \\const  e  =  f;
-//    ,
-//        \\const a = b;
-//        \\// zig fmt: off
-//        \\const  c  =  d;
-//        \\// zig fmt: on
-//        \\const e = f;
-//        \\
-//    );
-//}
-//
-//test "zig fmt: line comment following 'zig fmt: off'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\// Test
-//        \\const  e  =  f;
-//    );
-//}
-//
-//test "zig fmt: doc comment following 'zig fmt: off'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\/// test
-//        \\const  e  =  f;
-//    );
-//}
-//
-//test "zig fmt: line and doc comment following 'zig fmt: off'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\// test 1
-//        \\/// test 2
-//        \\const  e  =  f;
-//    );
-//}
-//
-//test "zig fmt: doc and line comment following 'zig fmt: off'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\/// test 1
-//        \\// test 2
-//        \\const  e  =  f;
-//    );
-//}
-//
-//test "zig fmt: alternating 'zig fmt: off' and 'zig fmt: on'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\// zig fmt: on
-//        \\// zig fmt: off
-//        \\const  e  =  f;
-//        \\// zig fmt: off
-//        \\// zig fmt: on
-//        \\// zig fmt: off
-//        \\const  a  =  b;
-//        \\// zig fmt: on
-//        \\const c = d;
-//        \\// zig fmt: on
-//        \\
-//    );
-//}
-//
-//test "zig fmt: line comment following 'zig fmt: on'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\const  e  =  f;
-//        \\// zig fmt: on
-//        \\// test
-//        \\const e = f;
-//        \\
-//    );
-//}
-//
-//test "zig fmt: doc comment following 'zig fmt: on'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\const  e  =  f;
-//        \\// zig fmt: on
-//        \\/// test
-//        \\const e = f;
-//        \\
-//    );
-//}
-//
-//test "zig fmt: line and doc comment following 'zig fmt: on'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\const  e  =  f;
-//        \\// zig fmt: on
-//        \\// test1
-//        \\/// test2
-//        \\const e = f;
-//        \\
-//    );
-//}
-//
-//test "zig fmt: doc and line comment following 'zig fmt: on'" {
-//    try testCanonical(
-//        \\// zig fmt: off
-//        \\const  e  =  f;
-//        \\// zig fmt: on
-//        \\/// test1
-//        \\// test2
-//        \\const e = f;
-//        \\
-//    );
-//}
+test "zig fmt: comment to disable/enable zig fmt first" {
+    try testCanonical(
+        \\// Test trailing comma syntax
+        \\// zig fmt: off
+        \\
+        \\const struct_trailing_comma = struct { x: i32, y: i32, };
+    );
+}
+
+test "zig fmt: comment to disable/enable zig fmt" {
+    try testTransform(
+        \\const  a  =  b;
+        \\// zig fmt: off
+        \\const  c  =  d;
+        \\// zig fmt: on
+        \\const  e  =  f;
+    ,
+        \\const a = b;
+        \\// zig fmt: off
+        \\const  c  =  d;
+        \\// zig fmt: on
+        \\const e = f;
+        \\
+    );
+}
+
+test "zig fmt: line comment following 'zig fmt: off'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\// Test
+        \\const  e  =  f;
+    );
+}
+
+test "zig fmt: doc comment following 'zig fmt: off'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\/// test
+        \\const  e  =  f;
+    );
+}
+
+test "zig fmt: line and doc comment following 'zig fmt: off'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\// test 1
+        \\/// test 2
+        \\const  e  =  f;
+    );
+}
+
+test "zig fmt: doc and line comment following 'zig fmt: off'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\/// test 1
+        \\// test 2
+        \\const  e  =  f;
+    );
+}
+
+test "zig fmt: alternating 'zig fmt: off' and 'zig fmt: on'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\// zig fmt: on
+        \\// zig fmt: off
+        \\const  e  =  f;
+        \\// zig fmt: off
+        \\// zig fmt: on
+        \\// zig fmt: off
+        \\const  a  =  b;
+        \\// zig fmt: on
+        \\const c = d;
+        \\// zig fmt: on
+        \\
+    );
+}
+
+test "zig fmt: line comment following 'zig fmt: on'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\const  e  =  f;
+        \\// zig fmt: on
+        \\// test
+        \\const e = f;
+        \\
+    );
+}
+
+test "zig fmt: doc comment following 'zig fmt: on'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\const  e  =  f;
+        \\// zig fmt: on
+        \\/// test
+        \\const e = f;
+        \\
+    );
+}
+
+test "zig fmt: line and doc comment following 'zig fmt: on'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\const  e  =  f;
+        \\// zig fmt: on
+        \\// test1
+        \\/// test2
+        \\const e = f;
+        \\
+    );
+}
+
+test "zig fmt: doc and line comment following 'zig fmt: on'" {
+    try testCanonical(
+        \\// zig fmt: off
+        \\const  e  =  f;
+        \\// zig fmt: on
+        \\/// test1
+        \\// test2
+        \\const e = f;
+        \\
+    );
+}
+
+test "zig fmt: 'zig fmt: (off|on)' works in the middle of code" {
+    try testTransform(
+        \\test "" {
+        \\    const x = 42;
+        \\
+        \\    if (foobar) |y| {
+        \\    // zig fmt: off
+        \\            }// zig fmt: on
+        \\
+        \\    const  z  = 420;
+        \\}
+        \\
+    ,
+        \\test "" {
+        \\    const x = 42;
+        \\
+        \\    if (foobar) |y| {
+        \\        // zig fmt: off
+        \\            }// zig fmt: on
+        \\
+        \\    const z = 420;
+        \\}
+        \\
+    );
+}
 
 test "zig fmt: pointer of unknown length" {
     try testCanonical(
lib/std/zig/render.zig
@@ -30,6 +30,10 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: ast.Tree) Error!void {
     _ = try renderComments(ais, tree, 0, comment_end_loc);
 
     try renderMembers(ais, tree, tree.rootDecls());
+
+    if (ais.disabled_offset) |disabled_offset| {
+        try writeFixingWhitespace(ais.underlying_writer, tree.source[disabled_offset..]);
+    }
 }
 
 /// Render all members in the given slice, keeping empty lines where appropriate
@@ -1971,6 +1975,7 @@ fn renderComments(ais: *Ais, tree: ast.Tree, start: usize, end: usize) Error!boo
         const comment_start = index + offset;
         const newline = comment_start +
             mem.indexOfScalar(u8, tree.source[comment_start..end], '\n').?;
+
         const untrimmed_comment = tree.source[comment_start..newline];
         const trimmed_comment = mem.trimRight(u8, untrimmed_comment, &std.ascii.spaces);
 
@@ -1993,6 +1998,17 @@ fn renderComments(ais: *Ais, tree: ast.Tree, start: usize, end: usize) Error!boo
 
         try ais.writer().print("{s}\n", .{trimmed_comment});
         index = newline + 1;
+
+        if (ais.disabled_offset) |disabled_offset| {
+            if (mem.eql(u8, trimmed_comment, "// zig fmt: on")) {
+                // write the source for which formatting was disabled directly
+                // to the underlying writer, fixing up invaild whitespace
+                try writeFixingWhitespace(ais.underlying_writer, tree.source[disabled_offset..index]);
+                ais.disabled_offset = null;
+            }
+        } else if (mem.eql(u8, trimmed_comment, "// zig fmt: off")) {
+            ais.disabled_offset = index;
+        }
     }
 
     if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
@@ -2066,6 +2082,14 @@ fn tokenSliceForRender(tree: ast.Tree, token_index: ast.TokenIndex) []const u8 {
     return ret;
 }
 
+fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void {
+    for (slice) |byte| switch (byte) {
+        '\t' => try writer.writeAll(" " ** 4),
+        '\r' => {},
+        else => try writer.writeByte(byte),
+    };
+}
+
 fn nodeIsBlock(tag: ast.Node.Tag) bool {
     return switch (tag) {
         .block,
@@ -2145,6 +2169,14 @@ fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
 
         underlying_writer: UnderlyingWriter,
 
+        /// Offset into the source at which formatting has been disabled with
+        /// a `zig fmt: off` comment.
+        ///
+        /// If non-null, the AutoIndentingStream will not write any bytes
+        /// to the underlying writer. It will however continue to track the
+        /// indentation level.
+        disabled_offset: ?usize = null,
+
         indent_count: usize = 0,
         indent_delta: usize,
         current_line_empty: bool = true,
@@ -2183,7 +2215,7 @@ fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
             if (bytes.len == 0)
                 return @as(usize, 0);
 
-            try self.underlying_writer.writeAll(bytes);
+            if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes);
             if (bytes[bytes.len - 1] == '\n')
                 self.resetLine();
             return bytes.len;
@@ -2243,7 +2275,9 @@ fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
         fn applyIndent(self: *Self) Error!void {
             const current_indent = self.currentIndent();
             if (self.current_line_empty and current_indent > 0) {
-                try self.underlying_writer.writeByteNTimes(' ', current_indent);
+                if (self.disabled_offset == null) {
+                    try self.underlying_writer.writeByteNTimes(' ', current_indent);
+                }
                 self.applied_indent = current_indent;
             }