Repository: fredrikekre/Runic.jl (julia)

Issue:
Require (at least one) space before comment

Pull request description:
Closes #51.

Patch:
diff --git a/src/Runic.jl b/src/Runic.jl
index 0b32a3e..fd688f6 100644
--- a/src/Runic.jl
+++ b/src/Runic.jl
@@ -432,6 +432,7 @@ function format_node!(ctx::Context, node::Node)::Union{Node, Nothing, NullNode}
     @return_something spaces_around_keywords(ctx, node)
     @return_something spaces_in_import_using(ctx, node)
     @return_something spaces_in_export_public(ctx, node)
+    @return_something spaces_around_comments(ctx, node)
     @return_something no_spaces_around_colon_etc(ctx, node)
     @return_something parens_around_op_calls_in_colon(ctx, node)
     @return_something for_loop_use_in(ctx, node)
diff --git a/src/runestone.jl b/src/runestone.jl
index be89d25..d149c32 100644
--- a/src/runestone.jl
+++ b/src/runestone.jl
@@ -3355,3 +3355,43 @@ function remove_trailing_semicolon(ctx::Context, node::Node)
     seek(ctx.fmt_io, pos)
     return any_changed ? make_node(node, kids′) : nothing
 end
+
+function spaces_around_comments(ctx::Context, node::Node)
+    is_leaf(node) && return
+    pos = position(ctx.fmt_io)
+    kids = verified_kids(node)
+    kids′ = kids
+    # We assume that the previous node ends with ws, which should be true since the same
+    # pass here adds it if the first kid is a comment.
+    prev_kid_ends_with_ws = true
+    ws = Node(JuliaSyntax.SyntaxHead(K"Whitespace", JuliaSyntax.TRIVIA_FLAG), 1)
+    for (i, kid) in pairs(kids)
+        if kind(kid) === K"Comment" ||
+                (fl = first_leaf(kid); fl !== nothing && kind(fl) === K"Comment")
+            # TODO: In the case where the comment is found within the kid the whitespace
+            # should maybe be added right before the comment in the tree (which is how
+            # JuliaSyntax would have parsed the source if the space was already there). I
+            # don't know if this really matters though since it is already pretty random
+            # where whitespace ends up.
+            if !prev_kid_ends_with_ws
+                kids′ = kids′ === kids ? kids[1:(i - 1)] : kids′
+                push!(kids′, ws)
+                replace_bytes!(ctx, " ", 0)
+                accept_node!(ctx, ws)
+            end
+        end
+        if kids′ !== kids
+            push!(kids′, kid)
+        end
+        accept_node!(ctx, kid)
+        prev_kid_ends_with_ws = kind(kid) in KSet"Whitespace NewlineWs" ||
+            (ll = last_leaf(kid); ll !== nothing && kind(ll) in KSet"Whitespace NewlineWs")
+    end
+    # Reset the stream and return
+    seek(ctx.fmt_io, pos)
+    if kids === kids′
+        return nothing
+    else
+        return make_node(node, kids′)
+    end
+end


Test patch:
diff --git a/test/runtests.jl b/test/runtests.jl
index fa4539d..c32586d 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -76,6 +76,22 @@ end
         format_string("# comment \t ") == format_string("# comment\t \t") == "# comment"
 end
 
+@testset "space before comment" begin
+    for sp in ("", " ", "  ")
+        csp = sp == "" ? " " : sp
+        @test format_string("a$(sp)# comment") == "a$(csp)# comment"
+        @test format_string("1 + 1$(sp)# comment") == "1 + 1$(csp)# comment"
+        @test format_string("(a,$(sp)# comment\nb)") ==
+            "(\n    a,$(csp)# comment\n    b,\n)"
+        # Edgecase where the comment ends up as the first leaf inside the call
+        @test format_string("(a,$(sp)# comment\nb + b)") ==
+            "(\n    a,$(csp)# comment\n    b + b,\n)"
+    end
+    let str = "a = 1  # a comment\nab = 2 # ab comment\n"
+        @test format_string(str) == str
+    end
+end
+
 @testset "Hex/oct/bin literal integers" begin
     z(n) = "0"^n
     test_cases = [
@@ -193,6 +209,7 @@ end
 
 @testset "spaces in listlike" begin
     for sp in ("", " ", "  "), a in ("a", "a + a", "a(x)"), b in ("b", "b + b", "b(y)")
+        csp = sp == "" ? " " : sp # at least one space before comment (but more allowed)
         # tuple, call, dotcall, vect, ref
         for (o, c) in (("(", ")"), ("f(", ")"), ("@f(", ")"), ("f.(", ")"), ("[", "]"), ("T[", "]"))
             tr = o in ("f(", "@f(", "f.(") ? "" : ","
@@ -235,9 +252,9 @@ end
                 "$(o)\n    $(a),\n    $(b),\n$(c)"
             # trailing comments
             @test format_string("$(o)$(sp)# x\n$(sp)$(a)$(sp),$(sp)# a\n$(sp)$(b)$(sp)# b\n$(c)") ==
-                "$(o)\n    # x\n    $(a),$(sp)# a\n    $(b)$(tr)$(sp)# b\n$(c)"
+                "$(o)\n    # x\n    $(a),$(csp)# a\n    $(b)$(tr)$(csp)# b\n$(c)"
             @test format_string("$(o)$(sp)# x\n$(sp)$(a)$(sp),$(sp)# a\n$(sp)$(b),$(sp)# b\n$(c)") ==
-                "$(o)\n    # x\n    $(a),$(sp)# a\n    $(b),$(sp)# b\n$(c)"
+                "$(o)\n    # x\n    $(a),$(csp)# a\n    $(b),$(csp)# b\n$(c)"
             # comments on separate lines between items
             @test format_string("$(o)\n# a\n$(a)$(sp),\n# b\n$(b)\n$(c)") ==
                 "$(o)\n    # a\n    $(a),\n    # b\n    $(b)$(tr)\n$(c)"
@@ -259,7 +276,7 @@ end
             @test format_string("$(a)$(sp),\n$(sp)$(b)") == "$(a),\n    $(b)"
             # trailing comments
             @test format_string("$(a)$(sp),$(sp)# a\n$(sp)$(b)$(sp)# b") ==
-                "$(a),$(sp)# a\n    $(b)$(sp)# b"
+                "$(a),$(csp)# a\n    $(b)$(csp)# b"
             @test format_string("# a\n$(a)$(sp),\n# b\n$(b)") ==
                 "# a\n$(a),\n    # b\n    $(b)"
         end