Repository: domluna/JuliaFormatter.jl (julia)

Issue:
`{1; 2; 3}` is formatted to `{123}`
```julia
julia> txt = "{1; 2; 3}"
"{1; 2; 3}"

julia> format_text(txt)
"{123}"

julia> Meta.parse(txt)
:({1; 2; 3})

julia> Meta.parse(format_text(txt))
:({123})

(tmp9_o3s8jg.julia_tmp) pkg> st -m
Status `/tmp/tmp9_o3s8jg.julia_tmp/Manifest.toml`
  [00ebfdb7] CSTParser v2.5.0
  [864edb3b] DataStructures v0.17.19
  [98e50ef6] JuliaFormatter v0.6.6
  [bac558e1] OrderedCollections v1.3.0
  [0796e94c] Tokenize v0.5.8
  [2a0f44e3] Base64
  [ade2ca70] Dates
  [8ba89e20] Distributed
  [b77e0a4c] InteractiveUtils
  [76f85450] LibGit2
  [8f399da3] Libdl
  [56ddb016] Logging
  [d6f4376e] Markdown
  [44cfe95a] Pkg
  [de0858da] Printf
  [3fa0cd96] REPL
  [9a3f8284] Random
  [ea8e919c] SHA
  [9e88b42a] Serialization
  [6462fe0b] Sockets
  [8dfed614] Test
  [cf7118a7] UUIDs
  [4ec0a83e] Unicode
```

Pull request:
fixes #260 

The BracesCat type wasn't being dispatched on since this type comes up very, very rarely.

This also adds a new leaf type INVERSETRAILINGSEMICOLON which behaves how TRAILINGSEMICOLON previously did. TRAILINGSEMICOLON is now behaves the same as TRAILINGCOMMA but with different punctuation obviously.

Patch:
diff --git a/src/fst.jl b/src/fst.jl
index e887cd60c..4870d7354 100644
--- a/src/fst.jl
+++ b/src/fst.jl
@@ -13,17 +13,12 @@
     INLINECOMMENT,
     TRAILINGCOMMA,
     TRAILINGSEMICOLON,
+    INVERSETRAILINGSEMICOLON,
 
     # no equivalent in CSTParser
     MacroBlock,
 )
 
-# nesting
-# DEFAULT
-# NEST
-# UNNEST
-# @enum()
-
 """
 Formatted Syntax Tree
 """
@@ -77,7 +72,9 @@ end
 @inline Semicolon() = FST(SEMICOLON, -1, -1, 0, 1, ";", nothing, nothing, false, 0)
 @inline TrailingComma() = FST(TRAILINGCOMMA, -1, -1, 0, 0, "", nothing, nothing, false, 0)
 @inline TrailingSemicolon() =
-    FST(TRAILINGSEMICOLON, -1, -1, 0, 1, ";", nothing, nothing, false, 0)
+    FST(TRAILINGSEMICOLON, -1, -1, 0, 0, "", nothing, nothing, false, 0)
+@inline InverseTrailingSemicolon() =
+    FST(INVERSETRAILINGSEMICOLON, -1, -1, 0, 1, ";", nothing, nothing, false, 0)
 @inline Whitespace(n) = FST(WHITESPACE, -1, -1, 0, n, " "^n, nothing, nothing, false, 0)
 @inline Placeholder(n) = FST(PLACEHOLDER, -1, -1, 0, n, " "^n, nothing, nothing, false, 0)
 @inline Notcode(startline, endline) =
@@ -152,6 +149,7 @@ function get_args(cst::CSTParser.EXPR)
         return get_args(cst.args[3:end])
     elseif cst.typ === CSTParser.Braces ||
            cst.typ === CSTParser.Vcat ||
+           cst.typ === CSTParser.BracesCat ||
            cst.typ === CSTParser.TupleH ||
            cst.typ === CSTParser.Vect ||
            cst.typ === CSTParser.InvisBrackets ||
diff --git a/src/nest.jl b/src/nest.jl
index f7e2a7c1e..5fbdaf4ce 100644
--- a/src/nest.jl
+++ b/src/nest.jl
@@ -61,12 +61,13 @@ function unnest!(fst::FST, nl_inds::Vector{Int})
     for (i, ind) in enumerate(nl_inds)
         fst[ind] = Whitespace(fst[ind].len)
         i == length(nl_inds) || continue
-        if fst[ind-1].typ === TRAILINGCOMMA
-            fst[ind-1].val = ""
-            fst[ind-1].len = 0
-        elseif fst[ind-1].typ === TRAILINGSEMICOLON
-            fst[ind-1].val = ";"
-            fst[ind-1].len = 1
+        pn = fst[ind-1]
+        if pn.typ === TRAILINGCOMMA || pn.typ === TRAILINGSEMICOLON
+            pn.val = ""
+            pn.len = 0
+        elseif pn.typ === INVERSETRAILINGSEMICOLON
+            pn.val = ";"
+            pn.len = 1
         elseif fst.typ === CSTParser.BinaryOpCall && fst[ind+1].typ === WHITESPACE
             # remove additional indent
             fst[ind+1] = Whitespace(0)
@@ -210,6 +211,8 @@ function nest!(ds::DefaultStyle, fst::FST, s::State)
         n_vcat!(style, fst, s)
     elseif fst.typ === CSTParser.Braces
         n_braces!(style, fst, s)
+    elseif fst.typ === CSTParser.BracesCat
+        n_bracescat!(style, fst, s)
     elseif fst.typ === CSTParser.InvisBrackets
         n_invisbrackets!(style, fst, s)
     elseif fst.typ === CSTParser.Comprehension
@@ -355,6 +358,10 @@ function n_tupleh!(ds::DefaultStyle, fst::FST, s::State)
                 n.len = 1
                 nest!(style, n, s)
             elseif n.typ === TRAILINGSEMICOLON
+                n.val = ";"
+                n.len = 1
+                nest!(style, n, s)
+            elseif n.typ === INVERSETRAILINGSEMICOLON
                 n.val = ""
                 n.len = 0
                 nest!(style, n, s)
@@ -392,6 +399,10 @@ n_vcat!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
 n_braces!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
     n_braces!(DefaultStyle(style), fst, s)
 
+@inline n_bracescat!(ds::DefaultStyle, fst::FST, s::State) = n_tupleh!(ds, fst, s)
+n_bracescat!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
+    n_bracescat!(DefaultStyle(style), fst, s)
+
 @inline n_parameters!(ds::DefaultStyle, fst::FST, s::State) = n_tupleh!(ds, fst, s)
 n_parameters!(style::S, fst::FST, s::State) where {S<:AbstractStyle} =
     n_parameters!(DefaultStyle(style), fst, s)
@@ -428,10 +439,6 @@ function n_comprehension!(ds::DefaultStyle, fst::FST, s::State; indent = -1)
             else
                 nest_if_over_margin!(style, fst, s, i)
             end
-        elseif n.typ === TRAILINGSEMICOLON
-            n.val = ""
-            n.len = 0
-            nest!(style, n, s)
         elseif i == length(fst.nodes) && !closer
             nest!(style, n, s)
         else
@@ -488,7 +495,7 @@ function n_call!(ds::DefaultStyle, fst::FST, s::State)
                 n.val = ","
                 n.len = 1
                 nest!(style, n, s)
-            elseif n.typ === TRAILINGSEMICOLON
+            elseif n.typ === INVERSETRAILINGSEMICOLON
                 n.val = ""
                 n.len = 0
                 nest!(style, n, s)
@@ -797,10 +804,6 @@ function n_block!(ds::DefaultStyle, fst::FST, s::State; indent = -1)
                 n.val = ","
                 n.len = 1
                 nest!(style, n, s)
-            elseif n.typ === TRAILINGSEMICOLON
-                n.val = ""
-                n.len = 0
-                nest!(style, n, s)
             elseif i < length(fst.nodes) - 1 && fst[i+2].typ === CSTParser.OPERATOR
                 # chainopcall / comparison
                 diff = fst.indent - fst[i].indent
diff --git a/src/pretty.jl b/src/pretty.jl
index 784b6fc83..866b590b0 100644
--- a/src/pretty.jl
+++ b/src/pretty.jl
@@ -59,6 +59,8 @@ function pretty(ds::DefaultStyle, cst::CSTParser.EXPR, s::State; kwargs...)
         return p_typedcomprehension(style, cst, s)
     elseif cst.typ === CSTParser.Braces
         return p_braces(style, cst, s)
+    elseif cst.typ === CSTParser.BracesCat
+        return p_bracescat(style, cst, s)
     elseif cst.typ === CSTParser.TupleH
         return p_tupleh(style, cst, s)
     elseif cst.typ === CSTParser.InvisBrackets
@@ -1481,6 +1483,34 @@ end
 p_braces(style::S, cst::CSTParser.EXPR, s::State) where {S<:AbstractStyle} =
     p_braces(DefaultStyle(style), cst, s)
 
+# BracesCat
+function p_bracescat(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
+    style = getstyle(ds)
+    t = FST(cst, nspaces(s))
+    nest = length(cst) > 2 && !(length(cst) == 3 && unnestable_arg(cst[2]))
+
+    for (i, a) in enumerate(cst)
+        n = pretty(style, a, s)
+        if i == 1 && nest
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
+        elseif i == length(cst) && nest
+            add_node!(t, TrailingSemicolon(), s)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, n, s, join_lines = true)
+        else
+            add_node!(t, n, s, join_lines = true)
+            if i != length(cst) - 1
+                add_node!(t, Semicolon(), s)
+                add_node!(t, Placeholder(1), s)
+            end
+        end
+    end
+    t
+end
+p_bracescat(style::S, cst::CSTParser.EXPR, s::State) where {S<:AbstractStyle} =
+    p_bracescat(DefaultStyle(style), cst, s)
+
 # Vect
 function p_vect(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
     style = getstyle(ds)
@@ -1645,7 +1675,8 @@ function p_vcat(ds::DefaultStyle, cst::CSTParser.EXPR, s::State)
         elseif !is_closer(a) && i > st
             add_node!(t, n, s, join_lines = true)
             if i != length(cst) - 1
-                has_semicolon(s.doc, n.startline) && add_node!(t, TrailingSemicolon(), s)
+                has_semicolon(s.doc, n.startline) &&
+                    add_node!(t, InverseTrailingSemicolon(), s)
                 add_node!(t, Placeholder(1), s)
                 # Keep trailing semicolon if there's only one arg
             elseif n_args(cst) == 1


Test patch:
diff --git a/test/default_style.jl b/test/default_style.jl
index b49ee25d9..d727cef10 100644
--- a/test/default_style.jl
+++ b/test/default_style.jl
@@ -4795,4 +4795,19 @@ some_function(
             )"""
         @test fmt(str_, 4, 1) == str
     end
+
+    @testset "issue 260 - BracesCat" begin
+        str = "{1; 2; 3}"
+        @test fmt(str, 4, length(str)) == str
+
+        str_ = "{1; 2; 3}"
+        str = """
+        {
+          1;
+          2;
+          3;
+        }"""
+        @test fmt(str_, 2, length(str_) - 1) == str
+        @test fmt(str, 2, length(str_)) == str_
+    end
 end