Repository: fredrikekre/Runic.jl
Pull request #62 (2024-09-10 15:52:31)

Fix insertion of space before comment
This patch changes where spaces are inserted before comments so that it is always added as a sibling to the comment instead of as a "uncle" when a comment is found as the first leaf of a kid.


Patch:
diff --git a/src/chisels.jl b/src/chisels.jl
index acbee17..b23af5c 100644
--- a/src/chisels.jl
+++ b/src/chisels.jl
@@ -423,6 +423,21 @@ function replace_last_leaf(node::Node, kid′::Union{Node, NullNode})
     end
 end
 
+# Insert a node before the first leaf (at the same level)
+# TODO: Currently only works for inserting a space before a comment
+function add_before_first_leaf(node::Node, kid′::Union{Node, NullNode})
+    @assert !is_leaf(node)
+    kids = verified_kids(node)
+    @assert length(kids) > 0
+    kids′ = copy(kids)
+    if kind(kids′[1]) === K"Comment"
+        pushfirst!(kids′, kid′)
+    else
+        kids′[1] = add_before_first_leaf(kids′[1], kid′)
+    end
+    return make_node(node, kids′)
+end
+
 function last_leaf(node::Node)
     if is_leaf(node)
         return node
diff --git a/src/runestone.jl b/src/runestone.jl
index 00dbabc..aab4267 100644
--- a/src/runestone.jl
+++ b/src/runestone.jl
@@ -3372,26 +3372,33 @@ function spaces_around_comments(ctx::Context, node::Node)
     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′
+        kid′ = kid
+        if !prev_kid_ends_with_ws && (
+                kind(kid) === K"Comment" ||
+                    (fl = first_leaf(kid); fl !== nothing && kind(fl) === K"Comment")
+            )
+            @assert !prev_kid_ends_with_ws
+            kids′ = kids′ === kids ? kids[1:(i - 1)] : kids′
+            replace_bytes!(ctx, " ", 0)
+            if kind(kid) === K"Comment"
                 push!(kids′, ws)
-                replace_bytes!(ctx, " ", 0)
                 accept_node!(ctx, ws)
+            else
+                @assert (fl = first_leaf(kid); fl !== nothing && kind(fl) === K"Comment")
+                # When the comment is found within the kid the whitespace is added right
+                # before the comment inside of the kid instead of in this outer context.
+                # This does not necessarily match how JuliaSyntax would have parsed it, but
+                # seems to work better than the alternative.
+                kid′ = add_before_first_leaf(kid, ws)
+                @assert span(kid′) == span(kid) + 1
             end
         end
         if kids′ !== kids
-            push!(kids′, kid)
+            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")
+        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)


Test patch:
diff --git a/test/runtests.jl b/test/runtests.jl
index a0943d4..bbe604f 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -83,9 +83,10 @@ end
         @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
+        # Edgecases where the comment ends up as the first leaf inside a node
         @test format_string("(a,$(sp)# comment\nb + b)") ==
             "(\n    a,$(csp)# comment\n    b + b,\n)"
+        @test format_string("if c$(sp)# a\n    b\nend") == "if c$(csp)# a\n    b\nend"
     end
     let str = "a = 1  # a comment\nab = 2 # ab comment\n"
         @test format_string(str) == str