Repository: domluna/JuliaFormatter.jl
Pull request #46 (2019-09-02 01:18:46)

All the comments
Prints all the comments, solves #28.

Essentially if any comment if detected (inline or otherwise) inside a `PTree`, a nest is forced. This allows   all the comments to be collected and printed appropriately.

Example:

```julia
var = foo(
    a, b, # comment 1
    c, # comment 2
    d # comment 3
)
```

Will be formatted to

```julia
var = foo(
    a, 
    b, # comment 1
    c, # comment 2
    d # comment 3
)
```

Staying as the former makes more sense if the comment relates to all the argument on the line. This could be done with some more effort but the current result is not far from optimal anyway.

A case where the comment still gets eaten is if there is a single argument with a comment:

```julia
var = foo(
    a # comment
)
```

Will be formatted to

```julia
var = foo(a)
```

Again with a bit more effort we could make this change, but 

1. This use of an inline comment is very rare
2. Debatable whether it's a good use

### TODO

- [x] Possibly un-nest if no leading comments are detected
- [x] Test comments inside `{...}` of `WhereOpCall`


Patch:
diff --git a/src/JuliaFormatter.jl b/src/JuliaFormatter.jl
index 7b20c8bb..15ae80a6 100644
--- a/src/JuliaFormatter.jl
+++ b/src/JuliaFormatter.jl
@@ -89,6 +89,7 @@ State(doc, indent_size, margin) = State(doc, indent_size, 0, 1, 0, margin)
 
 @inline nspaces(s::State) = s.indent
 @inline getline(d::Document, line::Int) = d.text[d.line_to_range[line]]
+@inline hascomment(d::Document, line::Int) = haskey(d.comments, line)
 
 @inline function cursor_loc(s::State, offset::Int)
     for (l, r) in enumerate(s.doc.ranges)
@@ -132,7 +133,7 @@ function format_text(text::AbstractString; indent::Integer = 4, margin::Integer
 
     s = State(d, indent, margin)
     t = pretty(x, s)
-    add_node!(t, InlineComment(t.endline))
+    hascomment(s.doc, t.endline) && (add_node!(t, InlineComment(t.endline), s))
     nest!(t, s)
 
     io = IOBuffer()
@@ -144,8 +145,8 @@ function format_text(text::AbstractString; indent::Integer = 4, margin::Integer
     end
 
     if t.endline < length(s.doc.ranges)
-        add_node!(t, Newline())
-        add_node!(t, Notcode(t.endline + 1, length(s.doc.ranges)))
+        add_node!(t, Newline(), s)
+        add_node!(t, Notcode(t.endline + 1, length(s.doc.ranges)), s)
     end
 
     print_tree(io, t, s)
diff --git a/src/nest.jl b/src/nest.jl
index 91e04e28..545b78e4 100644
--- a/src/nest.jl
+++ b/src/nest.jl
@@ -18,7 +18,7 @@
 
 function walk(f, x::PTree, s::State)
     f(x, s)
-    is_leaf(x) && (return)
+    is_leaf(x) && return
     for n in x.nodes
         if n.typ === NEWLINE
             s.line_offset = x.indent
@@ -29,12 +29,12 @@ function walk(f, x::PTree, s::State)
 end
 
 function reset_line_offset!(x::PTree, s::State)
-    !is_leaf(x) && (return)
+    !is_leaf(x) && return
     s.line_offset += length(x)
 end
 
 function add_indent!(x, s, indent)
-    indent == 0 && (return)
+    indent == 0 && return
     lo = s.line_offset
     f = (x::PTree, s::State) -> x.indent += indent
     walk(f, x, s)
@@ -132,17 +132,22 @@ end
 function n_import!(x, s; extra_width = 0)
     line_width = s.line_offset + length(x) + extra_width
     idx = findfirst(n -> n.typ === PLACEHOLDER, x.nodes)
-    if idx !== nothing && line_width > s.margin
-        # -3 due to the placeholder being ahead of a comma
-        # and another node
-        x.indent = s.line_offset + sum(length.(x.nodes[1:idx-3]))
+    if idx !== nothing && (line_width > s.margin || x.force_nest)
+        sidx = findfirst(n -> is_colon(n), x.nodes)
+        if sidx === nothing
+            sidx = 2
+        else
+            # for the WHITESPACE node after the colon
+            sidx += 1
+        end
+        x.indent = s.line_offset + sum(length.(x.nodes[1:sidx]))
         s.line_offset = x.indent
 
-        for (i, n) in enumerate(x.nodes[idx-2:end])
+        for (i, n) in enumerate(x.nodes)
             if n.typ === NEWLINE
                 s.line_offset = x.indent
             elseif n.typ === PLACEHOLDER
-                x.nodes[i+idx-3] = Newline()
+                x.nodes[i] = Newline()
                 s.line_offset = x.indent
             else
                 nest!(n, s)
@@ -158,7 +163,7 @@ function n_tuple!(x, s; extra_width = 0)
     idx = findlast(n -> n.typ === PLACEHOLDER, x.nodes)
     opener = is_opener(x.nodes[1])
     # @info "ENTERING" x.typ s.line_offset length(x) extra_width
-    if idx !== nothing && line_width > s.margin
+    if idx !== nothing && (line_width > s.margin || x.force_nest)
         # @debug "ENTERING" x.indent s.line_offset x.typ
         if opener
             x.nodes[end].indent = x.indent
@@ -222,7 +227,7 @@ function n_call!(x, s; extra_width = 0)
     line_width = s.line_offset + length(x) + extra_width
     idx = findlast(n -> n.typ === PLACEHOLDER, x.nodes)
     # @info "ENTERING" x.typ s.line_offset length(x) extra_width
-    if idx !== nothing && line_width > s.margin
+    if idx !== nothing && (line_width > s.margin || x.force_nest)
         x.nodes[end].indent = x.indent
         line_offset = s.line_offset
 
@@ -265,7 +270,7 @@ n_macrocall!(x, s; extra_width = 0) = n_call!(x, s, extra_width = extra_width)
 function n_wherecall!(x, s; extra_width = 0)
     line_width = s.line_offset + length(x) + extra_width
     # @debug "" s.line_offset x.typ line_width extra_width length(x)
-    if line_width > s.margin
+    if line_width > s.margin || x.force_nest
         line_offset = s.line_offset
         # after "A where "
         idx = findfirst(n -> n.typ === PLACEHOLDER, x.nodes)
@@ -299,7 +304,7 @@ function n_wherecall!(x, s; extra_width = 0)
                     x.nodes[end].indent = s.line_offset
                 end
                 nest!(n, s)
-            elseif n.typ === PLACEHOLDER && over
+            elseif n.typ === PLACEHOLDER && (over || x.force_nest)
                 x.nodes[i+idx] = Newline()
                 s.line_offset = x.indent
             elseif has_braces
@@ -323,7 +328,7 @@ end
 function n_chainopcall!(x, s; extra_width = 0)
     line_width = s.line_offset + length(x) + extra_width
     # @info "ENTERING" x.typ extra_width s.line_offset length(x)
-    if line_width > s.margin
+    if line_width > s.margin || x.force_nest
         line_offset = s.line_offset
         x.indent = s.line_offset
         phs = reverse(findall(n -> n.typ === PLACEHOLDER, x.nodes))
@@ -334,7 +339,8 @@ function n_chainopcall!(x, s; extra_width = 0)
                 nidx = phs[i-1]
                 l1 = sum(length.(x.nodes[1:idx-1]))
                 l2 = sum(length.(x.nodes[idx:nidx-1]))
-                if line_offset + l1 + l2 > s.margin
+                width = line_offset + l1 + l2
+                if x.force_nest || width > s.margin
                     x.nodes[idx] = Newline()
                 end
             end
@@ -355,16 +361,16 @@ function n_chainopcall!(x, s; extra_width = 0)
 
         s.line_offset = line_offset
         for (i, n) in enumerate(x.nodes)
-            if n.typ === NEWLINE
+            if n.typ === NEWLINE && !is_comment(x.nodes[i+1]) && !is_comment(x.nodes[i-1])
                 # +1 for newline to whitespace conversion
-                l = 1
+                width = s.line_offset + 1
                 if i == length(x.nodes) - 1
-                    l += sum(length.(x.nodes[i+1:end])) + extra_width
+                    width += sum(length.(x.nodes[i+1:end])) + extra_width
                 else
-                    l += sum(length.(x.nodes[i+1:i+3]))
+                    width += sum(length.(x.nodes[i+1:i+3]))
                 end
                 # @debug "" s.line_offset l  s.margin
-                if s.line_offset + l <= s.margin
+                if width <= s.margin
                     x.nodes[i] = Whitespace(1)
                 else
                     s.line_offset = x.indent
@@ -391,23 +397,24 @@ n_condcall!(x, s; extra_width = 0) = n_chainopcall!(x, s, extra_width = extra_wi
 # arg2
 function n_binarycall!(x, s; extra_width = 0)
     # If there's no placeholder the binary call is not nestable
-    idx = findlast(n -> n.typ === PLACEHOLDER, x.nodes)
+    idxs = findall(n -> n.typ === PLACEHOLDER, x.nodes)
     line_width = s.line_offset + length(x) + extra_width
-    # @info "ENTERING" x.typ extra_width s.line_offset length(x) idx
-    if idx !== nothing && line_width > s.margin
+    # @info "ENTERING" x.typ extra_width s.line_offset length(x) idxs
+    if length(idxs) == 2 && (line_width > s.margin || x.force_nest)
         line_offset = s.line_offset
-        x.nodes[idx-1] = Newline()
+        i1 = idxs[1]
+        i2 = idxs[2]
+        x.nodes[i1] = Newline()
 
         has_eq = CSTParser.defines_function(x.ref[]) || nest_assignment(x.ref[])
         if has_eq
             s.line_offset = x.indent + s.indent_size
-            x.nodes[idx] = Whitespace(s.indent_size)
+            x.nodes[i2] = Whitespace(s.indent_size)
             add_indent!(x.nodes[end], s, s.indent_size)
         else
             x.indent = s.line_offset
         end
 
-
         # arg2
         nest!(x.nodes[end], s, extra_width = extra_width)
 
@@ -417,18 +424,20 @@ function n_binarycall!(x, s; extra_width = 0)
         ew = length(x.nodes[2]) + length(x.nodes[3])
         # @debug "DURING" ew s.line_offset x.typ
         nest!(x.nodes[1], s, extra_width = ew)
-        for n in x.nodes[2:idx-1]
+        for n in x.nodes[2:i1]
             nest!(n, s)
         end
 
-        # @debug "BEFORE RESET" x.indent s.line_offset x.typ extra_width x.nodes[idx-2] length(x.nodes[idx+1])
+        # @debug "BEFORE RESET" x.indent s.line_offset x.typ extra_width x.nodes[i2-2] length(x.nodes[i2+1])
         # Undo nest if possible, +1 for whitespace
-        line_width = s.line_offset + length(x.nodes[idx+1]) + extra_width + 1
-        if line_width <= s.margin
-            x.nodes[idx-1] = Whitespace(1)
-            if has_eq
-                x.nodes[idx] = Placeholder(0)
-                add_indent!(x.nodes[end], s, -s.indent_size)
+        if !x.force_nest
+            line_width = s.line_offset + length(x.nodes[i2+1]) + extra_width + 1
+            if line_width <= s.margin
+                x.nodes[i1] = Whitespace(1)
+                if has_eq
+                    x.nodes[i2] = Placeholder(0)
+                    add_indent!(x.nodes[end], s, -s.indent_size)
+                end
             end
         end
 
diff --git a/src/pretty.jl b/src/pretty.jl
index 158d2d36..8c7660b6 100644
--- a/src/pretty.jl
+++ b/src/pretty.jl
@@ -11,21 +11,23 @@ mutable struct PTree
     val::Union{Nothing,AbstractString}
     nodes::Union{Nothing,Vector{PTree}}
     ref::Union{Nothing,Ref{CSTParser.EXPR}}
+    force_nest::Bool
 end
 
 PTree(x::CSTParser.EXPR, indent::Int) =
-    PTree(x.typ, -1, -1, indent, 0, nothing, PTree[], Ref(x))
+    PTree(x.typ, -1, -1, indent, 0, nothing, PTree[], Ref(x), false)
 
 function PTree(x::CSTParser.EXPR, startline::Int, endline::Int, val::AbstractString)
-    PTree(x.typ, startline, endline, 0, length(val), val, nothing, Ref(x))
+    PTree(x.typ, startline, endline, 0, length(val), val, nothing, Ref(x), false)
 end
 
-Newline() = PTree(NEWLINE, -1, -1, 0, 0, "\n", nothing, nothing)
-Semicolon() = PTree(SEMICOLON, -1, -1, 0, 1, ";", nothing, nothing)
-Whitespace(n) = PTree(WHITESPACE, -1, -1, 0, n, " "^n, nothing, nothing)
-Placeholder(n) = PTree(PLACEHOLDER, -1, -1, 0, n, " "^n, nothing, nothing)
-Notcode(startline, endline) = PTree(NOTCODE, startline, endline, 0, 0, "", nothing, nothing)
-InlineComment(line) = PTree(INLINECOMMENT, line, line, 0, 0, "", nothing, nothing)
+Newline() = PTree(NEWLINE, -1, -1, 0, 0, "\n", nothing, nothing, false)
+Semicolon() = PTree(SEMICOLON, -1, -1, 0, 1, ";", nothing, nothing, false)
+Whitespace(n) = PTree(WHITESPACE, -1, -1, 0, n, " "^n, nothing, nothing, false)
+Placeholder(n) = PTree(PLACEHOLDER, -1, -1, 0, n, " "^n, nothing, nothing, false)
+Notcode(startline, endline) =
+    PTree(NOTCODE, startline, endline, 0, 0, "", nothing, nothing, false)
+InlineComment(line) = PTree(INLINECOMMENT, line, line, 0, 0, "", nothing, nothing, false)
 
 Base.length(x::PTree) = x.len
 
@@ -34,6 +36,8 @@ empty_start(x::PTree) = x.startline == 1 && x.endline == 1 && x.val == ""
 
 is_punc(x) = CSTParser.ispunctuation(x)
 is_end(x) = x.typ === CSTParser.KEYWORD && x.val == "end"
+is_colon(x::PTree) = x.typ === CSTParser.OPERATOR && x.val == ":"
+is_comment(x::PTree) = x.typ === INLINECOMMENT || x.typ === NOTCODE
 
 is_colon_op(x) =
     (x.typ === CSTParser.BinaryOpCall && x.args[2].kind === Tokens.COLON) ||
@@ -52,7 +56,7 @@ function parent_is(x, typs...)
     false
 end
 
-function add_node!(t::PTree, n::PTree; join_lines = false, max_padding = -1)
+function add_node!(t::PTree, n::PTree, s::State; join_lines = false, max_padding = -1)
     if n.typ isa PLeaf
         t.len += length(n)
         # Don't want to alter the startline/endline of these types
@@ -67,8 +71,8 @@ function add_node!(t::PTree, n::PTree; join_lines = false, max_padding = -1)
     if n.typ === CSTParser.Block && length(n) == 0
         return
     elseif n.typ === CSTParser.Parameters
-        add_node!(t, Semicolon())
-        add_node!(t, Placeholder(1))
+        add_node!(t, Semicolon(), s)
+        add_node!(t, Placeholder(1), s)
     end
 
     if length(t.nodes) == 0
@@ -79,19 +83,39 @@ function add_node!(t::PTree, n::PTree; join_lines = false, max_padding = -1)
         return
     end
 
-    if !is_prev_newline(t.nodes[end]) && !join_lines
+    if !is_prev_newline(t.nodes[end])
         current_line = t.nodes[end].endline
         notcode_startline = current_line + 1
         notcode_endline = n.startline - 1
-
-        add_node!(t, InlineComment(current_line))
+        nt = t.nodes[end].typ
 
         if notcode_startline <= notcode_endline && n.typ !== CSTParser.LITERAL
-            add_node!(t, Newline())
-            add_node!(t, Notcode(notcode_startline, notcode_endline))
+            # If there are comments in between node elements
+            # nesting is forced in an effort to preserve them.
+            t.force_nest = true
+
+            hs = hascomment(s.doc, current_line)
+            hs && add_node!(t, InlineComment(current_line), s)
+            if nt !== PLACEHOLDER
+                add_node!(t, Newline(), s)
+            elseif hs && nt === PLACEHOLDER
+                # swap PLACEHOLDER (will be NEWLINE) with INLINECOMMENT node
+                idx = length(t.nodes)
+                t.nodes[idx-1], t.nodes[idx] = t.nodes[idx], t.nodes[idx-1]
+            end
+            add_node!(t, Notcode(notcode_startline, notcode_endline), s)
+            add_node!(t, Newline(), s)
+        elseif !join_lines
+            hascomment(s.doc, current_line) && add_node!(t, InlineComment(current_line), s)
+            add_node!(t, Newline(), s)
+        elseif nt === PLACEHOLDER &&
+               current_line != n.startline && hascomment(s.doc, current_line)
+            t.force_nest = true
+            add_node!(t, InlineComment(current_line), s)
+            # swap PLACEHOLDER (will be NEWLINE) with INLINECOMMENT node
+            idx = length(t.nodes)
+            t.nodes[idx-1], t.nodes[idx] = t.nodes[idx], t.nodes[idx-1]
         end
-
-        add_node!(t, Newline())
     end
 
     if n.startline < t.startline || t.startline == -1
@@ -280,7 +304,13 @@ function pretty(x::CSTParser.EXPR, s::State)
             continue
         end
         # @debug "" a a.typ
-        add_node!(t, pretty(a, s), join_lines = !is_fileh, max_padding = is_fileh ? 0 : -1)
+        add_node!(
+            t,
+            pretty(a, s),
+            s,
+            join_lines = !is_fileh,
+            max_padding = is_fileh ? 0 : -1
+        )
     end
     t
 end
@@ -394,12 +424,12 @@ function p_literal(x, s)
 
     # @debug "" lines x.val loc loc[2] sidx
 
-    t = PTree(CSTParser.StringH, -1, -1, loc[2], 0, nothing, PTree[], Ref(x))
+    t = PTree(CSTParser.StringH, -1, -1, loc[2], 0, nothing, PTree[], Ref(x), false)
     for (i, l) in enumerate(lines)
         ln = startline + i - 1
         l = i == 1 ? l : l[sidx:end]
-        tt = PTree(CSTParser.LITERAL, ln, ln, sidx, length(l), l, nothing, nothing)
-        add_node!(t, tt)
+        tt = PTree(CSTParser.LITERAL, ln, ln, sidx, length(l), l, nothing, nothing, false)
+        add_node!(t, tt, s)
     end
     t
 end
@@ -433,8 +463,8 @@ function p_stringh(x, s)
     for (i, l) in enumerate(lines)
         ln = startline + i - 1
         l = i == 1 ? l : l[sidx:end]
-        tt = PTree(CSTParser.LITERAL, ln, ln, sidx, length(l), l, nothing, nothing)
-        add_node!(t, tt)
+        tt = PTree(CSTParser.LITERAL, ln, ln, sidx, length(l), l, nothing, nothing, false)
+        add_node!(t, tt, s)
     end
     t
 end
@@ -447,12 +477,12 @@ function p_macrocall(x, s)
         loc = cursor_loc(s)
         # x.args[1] is empty and fullspan is 0 so we can skip it
         if x.args[2].typ === CSTParser.LITERAL
-            add_node!(t, p_literal(x.args[2], s), max_padding = 0)
+            add_node!(t, p_literal(x.args[2], s), s, max_padding = 0)
         elseif x.args[2].typ == CSTParser.StringH
-            add_node!(t, p_stringh(x.args[2], s))
+            add_node!(t, p_stringh(x.args[2], s), s)
         end
         loc = cursor_loc(s)
-        add_node!(t, pretty(x.args[3], s), max_padding = 0)
+        add_node!(t, pretty(x.args[3], s), s, max_padding = 0)
         return t
     end
 
@@ -467,33 +497,33 @@ function p_macrocall(x, s)
         n = pretty(a, s)
         if a.typ === CSTParser.MacroName
             if a.fullspan - a.span > 0 && length(x) > 1
-                add_node!(t, n, join_lines = true)
-                add_node!(t, Whitespace(1))
+                add_node!(t, n, s, join_lines = true)
+                add_node!(t, Whitespace(1), s)
             else
                 # assumes the next argument is a brace of some sort
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
             end
         elseif is_opener(n)
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         elseif is_closer(n)
-            add_node!(t, Placeholder(0))
-            add_node!(t, n, join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, n, s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         elseif a.fullspan - a.span > 0
             if has_closer && i < length(x) - 1
-                add_node!(t, n, join_lines = true)
-                add_node!(t, Whitespace(1))
+                add_node!(t, n, s, join_lines = true)
+                add_node!(t, Whitespace(1), s)
             elseif !has_closer && i < length(x)
-                add_node!(t, n, join_lines = true)
-                add_node!(t, Whitespace(1))
+                add_node!(t, n, s, join_lines = true)
+                add_node!(t, Whitespace(1), s)
             else
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
             end
         else
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         end
     end
     t
@@ -510,32 +540,32 @@ function p_block(x, s; ignore_single_line = false, from_quote = false)
         n = pretty(a, s)
         if from_quote && !single_line
             if i == 1 || CSTParser.is_comma(a)
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
             elseif CSTParser.is_comma(x.args[i-1])
-                add_node!(t, Whitespace(1))
-                add_node!(t, n, join_lines = true)
+                add_node!(t, Whitespace(1), s)
+                add_node!(t, n, s, join_lines = true)
             else
-                add_node!(t, Semicolon())
-                add_node!(t, n, max_padding = 0)
+                add_node!(t, Semicolon(), s)
+                add_node!(t, n, s, max_padding = 0)
             end
         elseif single_line
             if i == 1 || CSTParser.is_comma(a)
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
             elseif CSTParser.is_comma(x.args[i-1])
-                add_node!(t, Whitespace(1))
-                add_node!(t, n, join_lines = true)
+                add_node!(t, Whitespace(1), s)
+                add_node!(t, n, s, join_lines = true)
             else
-                add_node!(t, Semicolon())
-                add_node!(t, Whitespace(1))
-                add_node!(t, n, join_lines = true)
+                add_node!(t, Semicolon(), s)
+                add_node!(t, Whitespace(1), s)
+                add_node!(t, n, s, join_lines = true)
             end
         else
             if i < length(x) && CSTParser.is_comma(a) && is_punc(x.args[i+1])
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
             elseif CSTParser.is_comma(a) && i != length(x)
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
             else
-                add_node!(t, n, max_padding = 0)
+                add_node!(t, n, s, max_padding = 0)
             end
         end
     end
@@ -546,21 +576,21 @@ end
 # Abstract
 function p_abstract(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[3], s), join_lines = true)
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[4], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[3], s), s, join_lines = true)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[4], s), s, join_lines = true)
     t
 end
 
 # Primitive
 function p_primitive(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
     add_node!(t, pretty(x.args[2], s), join_lines = true)
     add_node!(t, Whitespace(1))
     add_node!(t, pretty(x.args[3], s), join_lines = true)
@@ -574,28 +604,29 @@ end
 # FunctionDef/Macro
 function p_function(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
     if length(x) > 3
         if x.args[3].fullspan == 0
-            add_node!(t, Whitespace(1))
-            add_node!(t, pretty(x.args[4], s), join_lines = true)
+            add_node!(t, Whitespace(1), s)
+            add_node!(t, pretty(x.args[4], s), s, join_lines = true)
         else
             s.indent += s.indent_size
             add_node!(
                 t,
                 p_block(x.args[3], s, ignore_single_line = true),
+                s,
                 max_padding = s.indent_size
             )
             s.indent -= s.indent_size
-            add_node!(t, pretty(x.args[4], s))
+            add_node!(t, pretty(x.args[4], s), s)
         end
     else
         # function stub, i.e. "function foo end"
         # this should be on one line
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[3], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[3], s), s, join_lines = true)
     end
     t
 end
@@ -603,21 +634,22 @@ end
 # Struct
 function p_struct(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
     if x.args[3].fullspan == 0
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[4], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[4], s), s, join_lines = true)
     else
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[3], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
-        add_node!(t, pretty(x.args[4], s))
+        add_node!(t, pretty(x.args[4], s), s)
     end
     t
 end
@@ -625,23 +657,24 @@ end
 # Mutable
 function p_mutable(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[3], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[3], s), s, join_lines = true)
     if x.args[4].fullspan == 0
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[5], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[5], s), s, join_lines = true)
     else
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[4], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
-        add_node!(t, pretty(x.args[5], s))
+        add_node!(t, pretty(x.args[5], s), s)
     end
     t
 end
@@ -649,15 +682,15 @@ end
 # ModuleH/BareModule
 function p_module(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
     if x.args[3].fullspan == 0
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[4], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[4], s), s, join_lines = true)
     else
-        add_node!(t, pretty(x.args[3], s), max_padding = 0)
-        add_node!(t, pretty(x.args[4], s))
+        add_node!(t, pretty(x.args[3], s), s, max_padding = 0)
+        add_node!(t, pretty(x.args[4], s), s)
     end
     t
 end
@@ -665,11 +698,11 @@ end
 # Const/Local/Global/Return
 function p_vardef(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
+    add_node!(t, pretty(x.args[1], s), s)
     if x.args[2].fullspan != 0
         for a in x.args[2:end]
-            add_node!(t, Whitespace(1))
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, Whitespace(1), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -683,8 +716,8 @@ function p_toplevel(x, s)
             s.offset += a.fullspan
             continue
         end
-        add_node!(t, pretty(a, s), max_padding = s.indent_size)
-        add_node!(t, Semicolon())
+        add_node!(t, pretty(a, s), s, max_padding = s.indent_size)
+        add_node!(t, Semicolon(), s)
     end
     t
 end
@@ -692,21 +725,21 @@ end
 # Begin
 function p_begin(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
+    add_node!(t, pretty(x.args[1], s), s)
     if x.args[2].fullspan == 0
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[3], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[3], s), s, join_lines = true)
     else
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[2], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
-        add_node!(t, pretty(x.args[3], s))
+        add_node!(t, pretty(x.args[3], s), s)
     end
-    # t.len = length(t.nodes[1])
     t
 end
 
@@ -714,24 +747,24 @@ end
 function p_quote(x, s)
     t = PTree(x, nspaces(s))
     if x.args[1].typ === CSTParser.KEYWORD && x.args[1].kind === Tokens.QUOTE
-        add_node!(t, pretty(x.args[1], s))
+        add_node!(t, pretty(x.args[1], s), s)
         if x.args[2].fullspan == 0
-            add_node!(t, Whitespace(1))
-            add_node!(t, pretty(x.args[3], s), join_lines = true)
+            add_node!(t, Whitespace(1), s)
+            add_node!(t, pretty(x.args[3], s), s, join_lines = true)
         else
             s.indent += s.indent_size
             add_node!(
                 t,
                 p_block(x.args[2], s, ignore_single_line = true),
+                s,
                 max_padding = s.indent_size
             )
             s.indent -= s.indent_size
-            add_node!(t, pretty(x.args[3], s))
+            add_node!(t, pretty(x.args[3], s), s)
         end
-        # t.len = length(t.nodes[1])
     else
         for a in x.args
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -750,23 +783,24 @@ end
 # end
 function p_let(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
+    add_node!(t, pretty(x.args[1], s), s)
     if length(x.args) > 3
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[2], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[2], s), s, join_lines = true)
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[3], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
-        add_node!(t, pretty(x.args[end], s))
+        add_node!(t, pretty(x.args[end], s), s)
     else
         s.indent += s.indent_size
-        add_node!(t, p_block(x.args[2], s, ignore_single_line = true))
+        add_node!(t, p_block(x.args[2], s, ignore_single_line = true), s)
         s.indent -= s.indent_size
-        add_node!(t, pretty(x.args[end], s))
+        add_node!(t, pretty(x.args[end], s), s)
     end
     t
 end
@@ -809,43 +843,45 @@ end
 # For/While
 function p_loop(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
     if x.args[1].kind === Tokens.FOR
         eq_to_in_normalization!(x.args[2])
     end
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
     s.indent += s.indent_size
     add_node!(
         t,
         p_block(x.args[3], s, ignore_single_line = true),
+        s,
         max_padding = s.indent_size
     )
     s.indent -= s.indent_size
-    add_node!(t, pretty(x.args[4], s))
+    add_node!(t, pretty(x.args[4], s), s)
     t
 end
 
 # Do
 function p_do(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
     if x.args[3].fullspan != 0
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[3], s), join_lines = true)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[3], s), s, join_lines = true)
     end
     if x.args[4].typ === CSTParser.Block
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[4], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
     end
-    add_node!(t, pretty(x.args[end], s))
+    add_node!(t, pretty(x.args[end], s), s)
     t
 end
 
@@ -855,22 +891,23 @@ function p_try(x, s)
     for a in x.args
         if a.fullspan == 0
         elseif a.typ === CSTParser.KEYWORD
-            add_node!(t, pretty(a, s), max_padding = 0)
+            add_node!(t, pretty(a, s), s, max_padding = 0)
         elseif a.typ === CSTParser.Block
             s.indent += s.indent_size
             add_node!(
                 t,
                 p_block(a, s, ignore_single_line = true),
+                s,
                 max_padding = s.indent_size
             )
             s.indent -= s.indent_size
         else
             len = length(t)
-            add_node!(t, Whitespace(1))
+            add_node!(t, Whitespace(1), s)
             n = pretty(a, s)
             # "catch n"
             t.len = max(len, 5 + 1 + length(n))
-            add_node!(t, n, join_lines = true, max_padding = 0)
+            add_node!(t, n, s, join_lines = true, max_padding = 0)
         end
     end
     t
@@ -880,24 +917,25 @@ end
 function p_if(x, s)
     t = PTree(x, nspaces(s))
     if x.args[1].typ === CSTParser.KEYWORD && x.args[1].kind === Tokens.IF
-        add_node!(t, pretty(x.args[1], s))
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(x.args[2], s), join_lines = true)
+        add_node!(t, pretty(x.args[1], s), s)
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(x.args[2], s), s, join_lines = true)
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[3], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
 
         len = length(t)
         if length(x.args) > 4
-            add_node!(t, pretty(x.args[4], s), max_padding = 0)
+            add_node!(t, pretty(x.args[4], s), s, max_padding = 0)
             if x.args[4].kind === Tokens.ELSEIF
-                add_node!(t, Whitespace(1))
+                add_node!(t, Whitespace(1), s)
                 n = pretty(x.args[5], s)
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
                 # "elseif n"
                 t.len = max(len, length(n))
             else
@@ -906,22 +944,24 @@ function p_if(x, s)
                 add_node!(
                     t,
                     p_block(x.args[5], s, ignore_single_line = true),
+                    s,
                     max_padding = s.indent_size
                 )
                 s.indent -= s.indent_size
             end
         end
         # END KEYWORD
-        add_node!(t, pretty(x.args[end], s))
+        add_node!(t, pretty(x.args[end], s), s)
     else
         # "cond" part of "elseif cond"
         t.len += 7
-        add_node!(t, pretty(x.args[1], s))
+        add_node!(t, pretty(x.args[1], s), s)
 
         s.indent += s.indent_size
         add_node!(
             t,
             p_block(x.args[2], s, ignore_single_line = true),
+            s,
             max_padding = s.indent_size
         )
         s.indent -= s.indent_size
@@ -929,12 +969,12 @@ function p_if(x, s)
         len = length(t)
         if length(x.args) > 2
             # this either else or elseif keyword
-            add_node!(t, pretty(x.args[3], s), max_padding = 0)
+            add_node!(t, pretty(x.args[3], s), s, max_padding = 0)
 
             if x.args[3].kind === Tokens.ELSEIF
-                add_node!(t, Whitespace(1))
+                add_node!(t, Whitespace(1), s)
                 n = pretty(x.args[4], s)
-                add_node!(t, n, join_lines = true)
+                add_node!(t, n, s, join_lines = true)
                 # "elseif n"
                 t.len = max(len, length(n))
             else
@@ -942,6 +982,7 @@ function p_if(x, s)
                 add_node!(
                     t,
                     p_block(x.args[4], s, ignore_single_line = true),
+                    s,
                     max_padding = s.indent_size
                 )
                 s.indent -= s.indent_size
@@ -957,16 +998,16 @@ function p_chaincall(x, s)
     for (i, a) in enumerate(x)
         n = pretty(a, s)
         if a.typ === CSTParser.OPERATOR
-            add_node!(t, Whitespace(1))
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, Whitespace(1), s)
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         elseif i == length(x) - 1 && is_punc(a) && is_punc(x.args[i+1])
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         elseif CSTParser.is_comma(a) && i != length(x)
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         else
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         end
     end
     t
@@ -977,11 +1018,11 @@ function p_kw(x, s)
     t = PTree(x, nspaces(s))
     for a in x
         if a.kind === Tokens.EQ
-            add_node!(t, Whitespace(1))
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, Whitespace(1), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1051,35 +1092,36 @@ function p_binarycall(x, s; nonest = false, nospace = false)
     end
 
     if x.args[1].typ === CSTParser.BinaryOpCall
-        add_node!(t, p_binarycall(x.args[1], s, nonest = nonest, nospace = nospace))
+        add_node!(t, p_binarycall(x.args[1], s, nonest = nonest, nospace = nospace), s)
     elseif x.args[1].typ === CSTParser.InvisBrackets
-        add_node!(t, p_invisbrackets(x.args[1], s, nonest = nonest, nospace = nospace))
+        add_node!(t, p_invisbrackets(x.args[1], s, nonest = nonest, nospace = nospace), s)
     else
-        add_node!(t, pretty(x.args[1], s))
+        add_node!(t, pretty(x.args[1], s), s)
     end
 
     narg2 = nest_arg2(x)
-    # @info "" narg2 nestable(x)
+    # @info "" nestable(x) !nonest narg2
+
+    narg2 && (t.force_nest = true)
+    nest = (nestable(x) && !nonest) || narg2
 
     if op.fullspan == 0
         # do nothing
     elseif (CSTParser.precedence(op) in (8, 13, 14, 16) && op.kind !== Tokens.ANON_FUNC) ||
            nospace
-        add_node!(t, pretty(op, s), join_lines = true)
-    elseif op.kind === Tokens.EX_OR || narg2
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(op, s), join_lines = true)
-    elseif nestable(x) && !nonest
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(op, s), join_lines = true)
+        add_node!(t, pretty(op, s), s, join_lines = true)
+    elseif op.kind === Tokens.EX_OR
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(op, s), s, join_lines = true)
+    elseif nest
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(op, s), s, join_lines = true)
         # for newline
-        add_node!(t, Placeholder(1))
-        # for indent, will be converted to `indent_size` if needed
-        add_node!(t, Placeholder(0))
+        add_node!(t, Placeholder(1), s)
     else
-        add_node!(t, Whitespace(1))
-        add_node!(t, pretty(op, s), join_lines = true)
-        add_node!(t, Whitespace(1))
+        add_node!(t, Whitespace(1), s)
+        add_node!(t, pretty(op, s), s, join_lines = true)
+        add_node!(t, Whitespace(1), s)
     end
 
 
@@ -1087,14 +1129,15 @@ function p_binarycall(x, s; nonest = false, nospace = false)
         n = p_binarycall(x.args[3], s, nonest = nonest, nospace = nospace)
     elseif x.args[3].typ === CSTParser.InvisBrackets
         n = p_invisbrackets(x.args[3], s, nonest = nonest, nospace = nospace)
-    elseif narg2
-        s.indent += s.indent_size
-        n = pretty(x.args[3], s)
-        s.indent -= s.indent_size
     else
         n = pretty(x.args[3], s)
     end
-    add_node!(t, n, join_lines = narg2 ? false : true)
+    add_node!(t, n, s, join_lines = true)
+
+    if nest
+        # for indent, will be converted to `indent_size` if needed
+        insert!(t.nodes, length(t.nodes), Placeholder(0))
+    end
     t
 end
 
@@ -1102,14 +1145,14 @@ end
 # A where B
 function p_wherecall(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
+    add_node!(t, pretty(x.args[1], s), s)
 
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
-    add_node!(t, Whitespace(1))
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
+    add_node!(t, Whitespace(1), s)
 
     # Used to mark where `B` starts.
-    add_node!(t, Placeholder(0))
+    add_node!(t, Placeholder(0), s)
 
     multi_arg = length(CSTParser.get_where_params(x)) > 1
     in_braces = CSTParser.is_lbrace(x.args[3])
@@ -1118,20 +1161,20 @@ function p_wherecall(x, s)
 
     for a in x.args[3:end]
         if is_opener(a) && multi_arg
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
             s.indent += s.indent_size
         elseif is_closer(a) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
             s.indent -= s.indent_size
         elseif CSTParser.is_comma(a)
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         elseif in_braces && a.typ === CSTParser.BinaryOpCall
-            add_node!(t, p_binarycall(a, s, nospace = true), join_lines = true)
+            add_node!(t, p_binarycall(a, s, nospace = true), s, join_lines = true)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1140,48 +1183,48 @@ end
 # Conditional
 function p_condcall(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
-    add_node!(t, Placeholder(1))
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
+    add_node!(t, Placeholder(1), s)
 
-    add_node!(t, pretty(x.args[3], s), join_lines = true)
-    add_node!(t, Whitespace(1))
-    add_node!(t, pretty(x.args[4], s), join_lines = true)
-    add_node!(t, Placeholder(1))
+    add_node!(t, pretty(x.args[3], s), s, join_lines = true)
+    add_node!(t, Whitespace(1), s)
+    add_node!(t, pretty(x.args[4], s), s, join_lines = true)
+    add_node!(t, Placeholder(1), s)
 
-    add_node!(t, pretty(x.args[5], s), join_lines = true)
+    add_node!(t, pretty(x.args[5], s), s, join_lines = true)
     t
 end
 
 # UnaryOpCall
 function p_unarycall(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
     t
 end
 
 function p_curly(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
 
     multi_arg = length(CSTParser.get_curly_params(x)) > 1
 
     if multi_arg
-        add_node!(t, Placeholder(0))
+        add_node!(t, Placeholder(0), s)
     end
 
     for (i, a) in enumerate(x.args[3:end])
         if i + 2 == length(x) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) - 3 && !is_punc(x.args[i+1])
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1189,24 +1232,24 @@ end
 
 function p_call(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, pretty(x.args[2], s), join_lines = true)
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, pretty(x.args[2], s), s, join_lines = true)
 
     multi_arg = length(CSTParser.get_args(x)) > 1
 
     if multi_arg
-        add_node!(t, Placeholder(0))
+        add_node!(t, Placeholder(0), s)
     end
 
     for (i, a) in enumerate(x.args[3:end])
         if i + 2 == length(x) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) - 3 && !is_punc(x.args[i+1])
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1219,30 +1262,32 @@ function p_invisbrackets(x, s; nonest = false, nospace = false)
 
     for (i, a) in enumerate(x)
         if a.typ === CSTParser.Block
-            add_node!(t, p_block(a, s, from_quote = true), join_lines = true)
+            add_node!(t, p_block(a, s, from_quote = true), s, join_lines = true)
         elseif a.typ === CSTParser.BinaryOpCall
             add_node!(
                 t,
                 p_binarycall(a, s, nonest = nonest, nospace = nospace),
+                s,
                 join_lines = true
             )
         elseif a.typ === CSTParser.InvisBrackets
-            add_node(
+            add_node!(
                 t,
                 p_invisbrackets(a, s, nonest = nonest, nospace = nospace),
+                s,
                 join_lines = true
             )
         elseif is_opener(a) && multi_arg
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         elseif is_closer(a) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1261,16 +1306,16 @@ function p_tuple(x, s)
     for (i, a) in enumerate(x)
         n = pretty(a, s)
         if is_opener(n) && multi_arg
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         elseif is_closer(n) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, n, join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, n, s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         else
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         end
     end
     t
@@ -1286,16 +1331,16 @@ function p_braces(x, s)
     for (i, a) in enumerate(x)
         n = pretty(a, s)
         if i == 1 && multi_arg
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         elseif i == length(x) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, n, join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, n, s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         else
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         end
     end
     t
@@ -1310,16 +1355,16 @@ function p_vect(x, s)
     for (i, a) in enumerate(x)
         n = pretty(a, s)
         if i == 1 && multi_arg
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(0))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(0), s)
         elseif i == length(x) && multi_arg
-            add_node!(t, Placeholder(0))
-            add_node!(t, n, join_lines = true)
+            add_node!(t, Placeholder(0), s)
+            add_node!(t, n, s, join_lines = true)
         elseif CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         else
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         end
     end
     t
@@ -1332,10 +1377,10 @@ function p_params(x, s)
     for (i, a) in enumerate(x)
         n = pretty(a, s)
         if CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, n, join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, n, s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         else
-            add_node!(t, n, join_lines = true)
+            add_node!(t, n, s, join_lines = true)
         end
     end
     t
@@ -1344,17 +1389,17 @@ end
 # Import, Export, Using, ImportAll
 function p_import(x, s)
     t = PTree(x, nspaces(s))
-    add_node!(t, pretty(x.args[1], s))
-    add_node!(t, Whitespace(1))
+    add_node!(t, pretty(x.args[1], s), s)
+    add_node!(t, Whitespace(1), s)
     for (i, a) in enumerate(x.args[2:end])
         if CSTParser.is_comma(a)
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Placeholder(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Placeholder(1), s)
         elseif CSTParser.is_colon(a)
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1365,22 +1410,24 @@ function p_ref(x, s)
     t = PTree(x, nspaces(s))
     for (i, a) in enumerate(x)
         if CSTParser.is_comma(a)
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         elseif a.typ === CSTParser.BinaryOpCall
             add_node!(
                 t,
                 p_binarycall(a, s, nonest = true, nospace = true),
+                s,
                 join_lines = true
             )
         elseif a.typ === CSTParser.InvisBrackets
             add_node!(
                 t,
                 p_invisbrackets(a, s, nonest = true, nospace = true),
+                s,
                 join_lines = true
             )
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1394,19 +1441,19 @@ function p_vcat(x, s)
 
     for (i, a) in enumerate(x)
         if !is_closer(a) && i > st
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
             if i != length(x) - 1
-                add_node!(t, Semicolon())
-                add_node!(t, Placeholder(1))
+                add_node!(t, Semicolon(), s)
+                add_node!(t, Placeholder(1), s)
             # Keep trailing semicolon if there's only one arg
             elseif !multi_arg
-                add_node!(t, Semicolon())
-                add_node!(t, Placeholder(0))
+                add_node!(t, Semicolon(), s)
+                add_node!(t, Placeholder(0), s)
             else
-                add_node!(t, Placeholder(0))
+                add_node!(t, Placeholder(0), s)
             end
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1418,10 +1465,10 @@ function p_hcat(x, s)
     st = x.typ === CSTParser.Hcat ? 1 : 2
     for (i, a) in enumerate(x)
         if i > st && i < length(x) - 1
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1432,10 +1479,10 @@ function p_row(x, s)
     t = PTree(x, nspaces(s))
     for (i, a) in enumerate(x)
         if i < length(x)
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t
@@ -1446,19 +1493,19 @@ function p_comprehension(x, s)
     t = PTree(x, nspaces(s))
     for (i, a) in enumerate(x)
         if a.typ === CSTParser.KEYWORD
-            add_node!(t, Whitespace(1))
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, Whitespace(1), s)
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
             if a.kind === Tokens.FOR
                 for j = i+1:length(x)
                     eq_to_in_normalization!(x.args[j])
                 end
             end
         elseif CSTParser.is_comma(a) && i < length(x) && !is_punc(x.args[i+1])
-            add_node!(t, pretty(a, s), join_lines = true)
-            add_node!(t, Whitespace(1))
+            add_node!(t, pretty(a, s), s, join_lines = true)
+            add_node!(t, Whitespace(1), s)
         else
-            add_node!(t, pretty(a, s), join_lines = true)
+            add_node!(t, pretty(a, s), s, join_lines = true)
         end
     end
     t


Test patch:
diff --git a/test/runtests.jl b/test/runtests.jl
index 81d5130c..420a31b1 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -32,9 +32,9 @@ end
 @testset "All" begin
 
     @testset "basic" begin
+        @test fmt("") == ""
         @test fmt("a") == "a"
         @test fmt("a  #foo") == "a  #foo"
-        @test fmt("") == ""
     end
 
     @testset "nofmt" begin
@@ -1160,6 +1160,113 @@ end
             # single comment ending in a subscriptₙ
             x- y
         end""") == str
+
+        str_ = """
+        var = foo(      # eat
+            a, b, # comment 1
+            c, # comment 2
+            # in between comment
+            d # comment 3
+        )        # pancakes"""
+        str = """
+        var = foo(      # eat
+            a,
+            b, # comment 1
+            c, # comment 2
+            # in between comment
+            d # comment 3
+        )        # pancakes"""
+        @test fmt(str_) == str
+
+        str_ = """
+        var = foo(      # eat
+            a, b, # comment 1
+            c, # comment 2
+            d # comment 3
+        )        # pancakes"""
+        str = """
+        var = foo(      # eat
+            a,
+            b, # comment 1
+            c, # comment 2
+            d # comment 3
+        )        # pancakes"""
+        @test fmt(str_) == str
+
+        str = """
+        A ? # foo
+        # comment 1
+
+        B :    # bar
+        # comment 2
+        C"""
+        @test fmt(str) == str
+
+        str = """
+        A ? B :
+         # comment
+
+        C"""
+        @test fmt(str) == str
+
+        str = """
+        A ? # foo
+        # comment 1
+
+        B : C"""
+        @test fmt(str) == str
+
+        str = """
+        begin
+            var = a +
+                # comment
+                  b
+        end
+        """
+        @test fmt(str) == str
+
+        str = """
+        begin
+            var = a +  # inline
+            # comment
+
+                  b
+        end
+        """
+        @test fmt(str) == str
+
+        str = """
+        begin
+            var = a +  # inline
+                  b
+        end
+        """
+        @test fmt(str) == str
+
+        str = """
+        foo() = 10 where {
+            A,
+                # comment
+            B
+        }"""
+        @test fmt(str) == str
+
+        str = """
+        foo() = 10 where Foo{
+            A,
+                # comment
+            B
+        }"""
+        @test fmt(str) == str
+
+        str = """
+        foo() = Foo(
+            A,
+                # comment
+            B
+        )"""
+        @test fmt(str) == str
+
     end
 
     @testset "pretty" begin
@@ -1553,6 +1660,7 @@ end
         e4"""
         @test fmt("cond1 ? e1 : cond2 ? e2 : cond3 ? e3 : e4", 4, 13) == str
 
+        # I'm an importer/exporter
         str = """
         export a,
                b"""