Browse Source

Fixes to listlike code

- Listlike expressions (e.g. tuples, calls, paren-blocks, vectors) that
   span multiple lines now always require a leading and a trailing
   newline (and thus a trailing comma from previous listlike spacing
   code). For example
   ```diff
   -f(a,
   -    b)
   +f(
   +    a,
   +    b,
   +)
   ```
 - Listlike expressions are now hard indents instead of the
   soft/continuation style from before. This looks better.
 - Vectors (K"vect" and K"ref") are now spaced like lists.
 - Fix some bugs in spaces-around-operators when there where hidden
   newlines at the beginning of the call chain.

Closes #9.
pull/19/head
Fredrik Ekre 1 year ago
parent
commit
e277ac21c7
No known key found for this signature in database
GPG Key ID: DE82E6D5E364C0A2
  1. 8
      src/Runic.jl
  2. 109
      src/chisels.jl
  3. 4
      src/debug.jl
  4. 2
      src/main.jl
  5. 330
      src/runestone.jl
  6. 156
      test/runtests.jl

8
src/Runic.jl

@ -99,9 +99,11 @@ include("chisels.jl")
# Return the result of expr if it doesn't evaluate to `nothing` # Return the result of expr if it doesn't evaluate to `nothing`
macro return_something(expr) macro return_something(expr)
return :(let node = $(esc(expr)) return :(
node === nothing || return node let node = $(esc(expr))
end) node === nothing || return node
end
)
end end
####################################################### #######################################################

109
src/chisels.jl

@ -238,6 +238,22 @@ function replace_first_leaf(node::Node, kid′::Union{Node, NullNode})
end end
end end
function replace_last_leaf(node::Node, kid′::Union{Node, NullNode})
if is_leaf(node)
return kid′
else
kids′ = copy(verified_kids(node))
kid′′ = replace_last_leaf(kids′[end], kid′)
if kid′′ === nullnode
pop!(kids′)
else
kids′[end] = kid′′
end
@assert length(kids′) > 0
return make_node(node, kids′)
end
end
function last_leaf(node::Node) function last_leaf(node::Node)
if is_leaf(node) if is_leaf(node)
return node return node
@ -272,6 +288,33 @@ function is_assignment(node::Node)
# return !is_leaf(node) && JuliaSyntax.is_prec_assignment(node) # return !is_leaf(node) && JuliaSyntax.is_prec_assignment(node)
end end
function unwrap_to_call_or_tuple(x)
is_leaf(x) && return nothing
@assert !is_leaf(x)
if kind(x) in KSet"call tuple"
return x
end
xkids = verified_kids(x)
xi = findfirst(x -> !JuliaSyntax.is_whitespace(x), xkids)::Int
return unwrap_to_call_or_tuple(xkids[xi])
end
function is_longform_anon_function(node::Node)
is_leaf(node) && return false
kind(node) === K"function" || return false
kids = verified_kids(node)
kw = findfirst(x -> kind(x) === K"function", kids)
@assert kw !== nothing
sig = findnext(x -> !JuliaSyntax.is_whitespace(x), kids, kw + 1)::Int
sigkid = kids[sig]
maybe_tuple = unwrap_to_call_or_tuple(sigkid)
if maybe_tuple === nothing
return false
else
return kind(maybe_tuple) === K"tuple"
end
end
# Just like `JuliaSyntax.is_infix_op_call`, but also check that the node is K"call" or # Just like `JuliaSyntax.is_infix_op_call`, but also check that the node is K"call" or
# K"dotcall" # K"dotcall"
function is_infix_op_call(node::Node) function is_infix_op_call(node::Node)
@ -319,6 +362,72 @@ function is_paren_block(node::Node)
return kind(node) === K"block" && JuliaSyntax.has_flags(node, JuliaSyntax.PARENS_FLAG) return kind(node) === K"block" && JuliaSyntax.has_flags(node, JuliaSyntax.PARENS_FLAG)
end end
function first_leaf_predicate(node::Node, pred::F) where {F}
if is_leaf(node)
return pred(node) ? node : nothing
else
kids = verified_kids(node)
for k in kids
r = first_leaf_predicate(k, pred)
if r !== nothing
return r
end
end
return nothing
end
end
function last_leaf_predicate(node::Node, pred::F) where {F}
if is_leaf(node)
return pred(node) ? node : nothing
else
kids = verified_kids(node)
for k in Iterators.reverse(kids)
r = first_leaf_predicate(k, pred)
if r !== nothing
return r
end
end
return nothing
end
end
function contains_outer_newline(kids::Vector{Node}, oidx::Int, cidx::Int; recurse = true)
pred = x -> kind(x) === K"NewlineWs" || !JuliaSyntax.is_whitespace(x)
for i in (oidx + 1):(cidx - 1)
kid = kids[i]
r = first_leaf_predicate(kid, pred)
if r !== nothing && kind(r) === K"NewlineWs"
return true
end
r = last_leaf_predicate(kid, pred)
if r !== nothing && kind(r) === K"NewlineWs"
return true
end
if kind(kid) === K"parameters"
grandkids = verified_kids(kid)
semiidx = findfirst(x -> kind(x) === K";", grandkids)::Int
r = contains_outer_newline(verified_kids(kid), semiidx, length(grandkids) + 1)
if r === true # r can be nothing so `=== true` is intentional
return true
end
end
end
return false
end
function any_leaf(pred::F, node::Node) where {F}
if is_leaf(node)
return pred(node)::Bool
else
kids = verified_kids(node)
for k in kids
any_leaf(pred, k) && return true
end
return false
end
end
########################## ##########################
# Utilities for IOBuffer # # Utilities for IOBuffer #
########################## ##########################

4
src/debug.jl

@ -22,8 +22,8 @@ function Base.showerror(io::IO, err::AssertionError)
print( print(
io, io,
"Runic.AssertionError: `", err.msg, "`. This is unexpected, " * "Runic.AssertionError: `", err.msg, "`. This is unexpected, " *
"please file an issue with a reproducible example at " * "please file an issue with a reproducible example at " *
"https://github.com/fredrikekre/Runic.jl/issues/new.", "https://github.com/fredrikekre/Runic.jl/issues/new.",
) )
end end

2
src/main.jl

@ -139,7 +139,7 @@ function main(argv)
if !(inplace || check || diff || outputfile !== nothing) if !(inplace || check || diff || outputfile !== nothing)
return panic( return panic(
"at least one of options `-c, --check`, `-d, --diff`, `-i, --inplace`, " * "at least one of options `-c, --check`, `-d, --diff`, `-i, --inplace`, " *
"or `-o, --output` must be specified", "or `-o, --output` must be specified",
) )
end end

330
src/runestone.jl

@ -289,9 +289,10 @@ end
# TODO: Why did this function become sooo complicated? # TODO: Why did this function become sooo complicated?
function spaces_in_listlike(ctx::Context, node::Node) function spaces_in_listlike(ctx::Context, node::Node)
if !( if !(
kind(node) in KSet"tuple parameters curly braces bracescat" || kind(node) in KSet"tuple parameters curly braces bracescat vect ref" ||
(kind(node) === K"call" && flags(node) == 0) || # Flag check rules out op-calls (kind(node) === K"call" && flags(node) == 0) || # Flag check rules out op-calls
(kind(node) === K"dotcall" && flags(node) == 0) (kind(node) === K"dotcall" && flags(node) == 0) ||
is_paren_block(node)
) )
return nothing return nothing
end end
@ -310,7 +311,7 @@ function spaces_in_listlike(ctx::Context, node::Node)
comma = Node(JuliaSyntax.SyntaxHead(K",", JuliaSyntax.TRIVIA_FLAG), 1) comma = Node(JuliaSyntax.SyntaxHead(K",", JuliaSyntax.TRIVIA_FLAG), 1)
# Find the opening and closing leafs # Find the opening and closing leafs
if kind(node) in KSet"tuple call dotcall" if kind(node) in KSet"tuple call dotcall" || is_paren_block(node)
opening_leaf_idx = findfirst(x -> kind(x) === K"(", kids) opening_leaf_idx = findfirst(x -> kind(x) === K"(", kids)
if opening_leaf_idx === nothing if opening_leaf_idx === nothing
# TODO: Implicit tuple without (), for example arguments in a do-block # TODO: Implicit tuple without (), for example arguments in a do-block
@ -322,6 +323,10 @@ function spaces_in_listlike(ctx::Context, node::Node)
opening_leaf_idx = findfirst(x -> kind(x) === K"{", kids)::Int opening_leaf_idx = findfirst(x -> kind(x) === K"{", kids)::Int
closing_leaf_idx = findnext(x -> kind(x) === K"}", kids, opening_leaf_idx + 1)::Int closing_leaf_idx = findnext(x -> kind(x) === K"}", kids, opening_leaf_idx + 1)::Int
closing_leaf_idx == opening_leaf_idx + 1 && return nothing # empty closing_leaf_idx == opening_leaf_idx + 1 && return nothing # empty
elseif kind(node) in KSet"vect ref"
opening_leaf_idx = findfirst(x -> kind(x) === K"[", kids)::Int
closing_leaf_idx = findnext(x -> kind(x) === K"]", kids, opening_leaf_idx + 1)::Int
closing_leaf_idx == opening_leaf_idx + 1 && return nothing # empty
else else
@assert kind(node) === K"parameters" @assert kind(node) === K"parameters"
opening_leaf_idx = findfirst(x -> kind(x) === K";", kids)::Int opening_leaf_idx = findfirst(x -> kind(x) === K";", kids)::Int
@ -341,30 +346,36 @@ function spaces_in_listlike(ctx::Context, node::Node)
last_comma_idx = nothing last_comma_idx = nothing
end end
# Multiline lists require leading and trailing newline
# multiline = contains_outer_newline(kids, opening_leaf_idx, closing_leaf_idx)
multiline = any(y -> any_leaf(x -> kind(x) === K"NewlineWs", kids[y]), (opening_leaf_idx + 1):(closing_leaf_idx - 1))
# A trailing comma is required if # A trailing comma is required if
# - node is a single item tuple (Julia-requirement) # - node is a single item tuple which is not from an anonymous fn (Julia-requirement)
# - the closing token is not on the same line as the last item (Runic-requirement) # - the closing token is not on the same line as the last item (Runic-requirement)
require_trailing_comma = false require_trailing_comma = false
if kind(node) === K"tuple" && n_items == 1 if kind(node) === K"tuple" && n_items == 1 && ctx.lineage_kinds[end] !== K"function"
# TODO: May also have to check for K"where" and K"::" in the lineage above
require_trailing_comma = true require_trailing_comma = true
elseif kind(node) === K"bracescat" elseif kind(node) in KSet"bracescat block"
require_trailing_comma = false # Leads to parser error require_trailing_comma = false # Leads to parser error
elseif kind(node) === K"parameters" elseif kind(node) === K"parameters"
# For parameters the trailing comma is configured from the parent # For parameters the trailing comma is configured from the parent
require_trailing_comma = has_tag(node, TAG_TRAILING_COMMA) require_trailing_comma = has_tag(node, TAG_TRAILING_COMMA)
elseif multiline
require_trailing_comma = true
elseif n_items > 0 elseif n_items > 0
require_trailing_comma = any( require_trailing_comma = any(
x -> kind(x) === K"NewlineWs", @view(kids[(last_item_idx + 1):(closing_leaf_idx - 1)]), x -> kind(x) === K"NewlineWs", @view(kids[(last_item_idx + 1):(closing_leaf_idx - 1)]),
) || has_newline_after_non_whitespace(kids[last_item_idx]) ) || has_newline_after_non_whitespace(kids[last_item_idx])
end end
expect_trailing_comma = require_trailing_comma
# Helper to compute the new state after a given item # Helper to compute the new state after a given item
function state_after_item(i) function state_after_item(i)
@assert i <= last_item_idx @assert i <= last_item_idx
if i < last_item_idx if i < last_item_idx
return :expect_comma return :expect_comma
elseif i == last_item_idx && expect_trailing_comma elseif i == last_item_idx && require_trailing_comma
return :expect_comma return :expect_comma
else else
return :expect_closing return :expect_closing
@ -372,9 +383,14 @@ function spaces_in_listlike(ctx::Context, node::Node)
end end
# Keep track of the state # Keep track of the state
state = kind(node) === K"parameters" ? (:expect_space) : state = if kind(node) === K"parameters"
n_items > 0 ? (:expect_item) : # @assert !multiline # TODO
(:expect_closing) :expect_space
elseif n_items > 0
:expect_item
else
:expect_closing
end
any_kid_changed = false any_kid_changed = false
pos = position(ctx.fmt_io) pos = position(ctx.fmt_io)
@ -432,9 +448,9 @@ function spaces_in_listlike(ctx::Context, node::Node)
end end
elseif state === :expect_comma elseif state === :expect_comma
trailing = i > last_item_idx trailing = i > last_item_idx
if kind(kid′) === K"," || (kind(kid′) === K";" && kind(node) === K"bracescat") if kind(kid′) === K"," || kind(kid′) === K";"
before_last_item = i < last_item_idx before_last_item = i < last_item_idx
if before_last_item || expect_trailing_comma if before_last_item || require_trailing_comma
# Nice, just accept it. # Nice, just accept it.
accept_node!(ctx, kid′) accept_node!(ctx, kid′)
any_kid_changed && push!(kids′, kid′) any_kid_changed && push!(kids′, kid′)
@ -464,7 +480,7 @@ function spaces_in_listlike(ctx::Context, node::Node)
next_kind = next_non_ws_idx === nothing ? nothing : kind(kids[next_non_ws_idx]) next_kind = next_non_ws_idx === nothing ? nothing : kind(kids[next_non_ws_idx])
# Insert a comma if there isn't one coming # Insert a comma if there isn't one coming
if trailing && next_kind !== K"," if trailing && next_kind !== K","
@assert expect_trailing_comma @assert require_trailing_comma
this_kid_changed = true this_kid_changed = true
if kids′ === kids if kids′ === kids
kids′ = kids[1:(i - 1)] kids′ = kids[1:(i - 1)]
@ -474,12 +490,15 @@ function spaces_in_listlike(ctx::Context, node::Node)
accept_node!(ctx, comma) accept_node!(ctx, comma)
state = :expect_closing state = :expect_closing
end end
if kind(kid′) === K"NewlineWs"
state = :expect_closing
end
any_kid_changed |= this_kid_changed any_kid_changed |= this_kid_changed
# Accept the newline # Accept the newline
accept_node!(ctx, kid′) accept_node!(ctx, kid′)
any_kid_changed && push!(kids′, kid′) any_kid_changed && push!(kids′, kid′)
elseif kind(kid′) === K"parameters" elseif kind(kid′) === K"parameters"
@assert kind(node) in KSet"call dotcall curly" # TODO: Can this happen for named tuples? @assert kind(node) in KSet"call dotcall curly tuple" # TODO: Can this happen for named tuples?
@assert i === last_item_idx @assert i === last_item_idx
@assert findnext( @assert findnext(
!JuliaSyntax.is_whitespace, @view(kids[1:(closing_leaf_idx - 1)]), i + 1, !JuliaSyntax.is_whitespace, @view(kids[1:(closing_leaf_idx - 1)]), i + 1,
@ -494,7 +513,7 @@ function spaces_in_listlike(ctx::Context, node::Node)
# kids′ = kids[1:i - 1] # kids′ = kids[1:i - 1]
# end # end
end end
if expect_trailing_comma && !has_tag(kid′, TAG_TRAILING_COMMA) if require_trailing_comma && !has_tag(kid′, TAG_TRAILING_COMMA)
# Tag the parameters node to require a trailing comma # Tag the parameters node to require a trailing comma
kid′ = add_tag(kid′, TAG_TRAILING_COMMA) kid′ = add_tag(kid′, TAG_TRAILING_COMMA)
this_kid_changed = true this_kid_changed = true
@ -612,8 +631,7 @@ function spaces_in_listlike(ctx::Context, node::Node)
if state !== :expect_closing if state !== :expect_closing
if state === :expect_comma if state === :expect_comma
# Need to add a trailing comma if it is expected # Need to add a trailing comma if it is expected
@assert state === :expect_comma @assert require_trailing_comma
@assert expect_trailing_comma
any_kid_changed = true any_kid_changed = true
if kids′ === kids if kids′ === kids
kids′ = kids[1:(closing_leaf_idx - 1)] kids′ = kids[1:(closing_leaf_idx - 1)]
@ -649,8 +667,8 @@ end
function spaces_around_operators(ctx::Context, node::Node) function spaces_around_operators(ctx::Context, node::Node)
if !( if !(
(is_infix_op_call(node) && !(kind(infix_op_call_op(node)) in KSet": ^")) || (is_infix_op_call(node) && !(kind(infix_op_call_op(node)) in KSet": ^")) ||
(kind(node) in KSet"<: >:" && meta_nargs(node) == 3) || (kind(node) in KSet"<: >:" && meta_nargs(node) == 3) ||
(kind(node) === K"comparison" && !JuliaSyntax.is_trivia(node)) (kind(node) === K"comparison" && !JuliaSyntax.is_trivia(node))
) )
return nothing return nothing
end end
@ -769,8 +787,8 @@ end
function no_spaces_around_colon_etc(ctx::Context, node::Node) function no_spaces_around_colon_etc(ctx::Context, node::Node)
if !( if !(
(is_infix_op_call(node) && kind(infix_op_call_op(node)) in KSet": ^") || (is_infix_op_call(node) && kind(infix_op_call_op(node)) in KSet": ^") ||
(kind(node) === K"::" && !is_leaf(node)) || (kind(node) === K"::" && !is_leaf(node)) ||
(kind(node) in KSet"<: >:" && meta_nargs(node) == 2) (kind(node) in KSet"<: >:" && meta_nargs(node) == 2)
) )
return nothing return nothing
end end
@ -781,14 +799,18 @@ end
# Single space around keywords: # Single space around keywords:
# Both sides of: `where`, `do` (if followed by arguments) # Both sides of: `where`, `do` (if followed by arguments)
# Right hand side of: `mutable`, `struct`, `abstract`, `primitive`, `type`, `function`, # Right hand side of: `mutable`, `struct`, `abstract`, `primitive`, `type`, `function` (if
# `if`, `elseif`, `catch` (if followed by variable) # named function), `if`, `elseif`, `catch` (if followed by variable)
function spaces_around_keywords(ctx::Context, node::Node) function spaces_around_keywords(ctx::Context, node::Node)
is_leaf(node) && return nothing is_leaf(node) && return nothing
keyword_set = KSet"where do mutable struct abstract primitive type function if elseif catch" keyword_set = KSet"where do mutable struct abstract primitive type function if elseif catch"
if !(kind(node) in keyword_set) if !(kind(node) in keyword_set)
return nothing return nothing
end end
if is_longform_anon_function(node)
# TODO: `function(` should have no space, handled elsewhere
return nothing
end
kids = verified_kids(node) kids = verified_kids(node)
kids′ = kids kids′ = kids
any_changes = false any_changes = false
@ -990,7 +1012,7 @@ end
function for_loop_use_in(ctx::Context, node::Node) function for_loop_use_in(ctx::Context, node::Node)
if !( if !(
(kind(node) === K"for" && !is_leaf(node) && meta_nargs(node) == 4) || (kind(node) === K"for" && !is_leaf(node) && meta_nargs(node) == 4) ||
(kind(node) === K"generator" && meta_nargs(node) == 3) # TODO: Unsure about 3. (kind(node) === K"generator" && meta_nargs(node) == 3) # TODO: Unsure about 3.
) )
return nothing return nothing
end end
@ -1177,13 +1199,18 @@ function indent_function_or_macro(ctx::Context, node::Node)
any_kid_changed = true any_kid_changed = true
end end
# Second node is the space between keyword and name # Second node is the space between keyword and name
# TODO: Make sure there is just a single space if !is_longform_anon_function(node)
space_idx = 2 space_idx = 2
space_node = kids[space_idx] space_node = kids[space_idx]
@assert is_leaf(space_node) && kind(space_node) === K"Whitespace" @assert is_leaf(space_node) && kind(space_node) === K"Whitespace"
end
# Third node is the signature (call/where/::) for standard method definitions but just # Third node is the signature (call/where/::) for standard method definitions but just
# an Identifier for cases like `function f end`. # an Identifier for cases like `function f end`.
sig_idx = 3 sig_idx = findnext(x -> !JuliaSyntax.is_whitespace(x), kids, func_idx + 1)::Int
if sig_idx == 2
# Only case where no space is needed after the keyword
@assert is_longform_anon_function(node)
end
sig_node = kids[sig_idx] sig_node = kids[sig_idx]
if kind(sig_node) === K"Identifier" if kind(sig_node) === K"Identifier"
# Empty function definition like `function f end`. # Empty function definition like `function f end`.
@ -1197,16 +1224,17 @@ function indent_function_or_macro(ctx::Context, node::Node)
end end
return any_kid_changed ? node : nothing return any_kid_changed ? node : nothing
end end
@assert !is_leaf(sig_node) && kind(sig_node) in KSet"call where ::" # K"tuple" when this is an anonymous function
@assert !is_leaf(sig_node) && kind(sig_node) in KSet"call where :: tuple"
# Fourth node is the function/macro body block. # Fourth node is the function/macro body block.
block_idx = 4 block_idx = sig_idx + 1
block_node′ = indent_block(ctx, kids[block_idx]) block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing if block_node′ !== nothing
kids[block_idx] = block_node′ kids[block_idx] = block_node′
any_kid_changed = true any_kid_changed = true
end end
# Fifth node is the closing end keyword # Fifth node is the closing end keyword
end_idx = 5 end_idx = block_idx + 1
end_node = kids[end_idx] end_node = kids[end_idx]
@assert is_leaf(end_node) && kind(end_node) === K"end" @assert is_leaf(end_node) && kind(end_node) === K"end"
if !has_tag(end_node, TAG_DEDENT) if !has_tag(end_node, TAG_DEDENT)
@ -1516,10 +1544,12 @@ function indent_newlines_between_indices(
this_kid_changed = true this_kid_changed = true
end end
# NewlineWs nodes can also hide as the first or last leaf of a node, tag'em. # NewlineWs nodes can also hide as the first or last leaf of a node, tag'em.
# Skip leading newline if this kid is the first one
leading = i != open_idx
# Skip trailing newline of this kid if the next token is the closing one and the # Skip trailing newline of this kid if the next token is the closing one and the
# closing token should not be indented. # closing token should not be indented.
trailing = !(i == close_idx - 1 && !indent_closing_token) trailing = !(i == close_idx - 1 && !indent_closing_token)
kid′ = continue_newlines(kid; leading = true, trailing = trailing) kid′ = continue_newlines(kid; leading = leading, trailing = trailing)
if kid′ !== nothing if kid′ !== nothing
kid = kid′ kid = kid′
this_kid_changed = true this_kid_changed = true
@ -1533,6 +1563,193 @@ function indent_newlines_between_indices(
return any_kid_changed ? node : nothing return any_kid_changed ? node : nothing
end end
# Tags opening and closing tokens for indent/dedent and the newline just before the closing
# token as pre-dedent
function indent_listlike(
ctx::Context, node::Node, open_idx::Int, close_idx::Int;
indent_closing_token::Bool = false,
)
kids = verified_kids(node)
kids′ = kids
any_kid_changed = false
# Bail early if there is just a single item
open_idx == close_idx && return nothing
# Check whether we expect leading/trailing newlines
# multiline = contains_outer_newline(kids, open_idx, close_idx)
multiline = any(y -> any_leaf(x -> kind(x) === K"NewlineWs", kids[y]), (open_idx + 1):(close_idx - 1))
if !multiline
# TODO: This should be fine? If there are no newlines it should be safe to just
# don't indent anything in this node?
return
end
pos = position(ctx.fmt_io)
# Leave all initial kids the same
for i in 1:(open_idx - 1)
accept_node!(ctx, kids[i])
end
# Opening token indents
kid = kids[open_idx]
@assert is_leaf(kid)
@assert kind(kid) !== K"NewlineWs"
if !has_tag(kid, TAG_INDENT)
kid = add_tag(kid, TAG_INDENT)
if kids′ === kids
kids′ = kids[1:(open_idx - 1)]
end
any_kid_changed = true
end
any_kid_changed && push!(kids′, kid)
accept_node!(ctx, kid)
# Next we expect the leading newline
@assert multiline
kid = kids[open_idx + 1]
if kind(kid) === K"NewlineWs" ||
kind(first_leaf(kid)) === K"NewlineWs"
# Newline or newlinde hidden in first item
any_kid_changed && push!(kids′, kid)
accept_node!(ctx, kid)
else
# Need to insert a newline
if kind(kid) === K"Whitespace"
# Merge with the whitespace. It shouldn't matter if the newline is put before or
# after the space. If put before the space will be handled by the indent pass
# and if put after it will be handled by the trailing spaces pass.
kid = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), span(kid) + 1)
replace_bytes!(ctx, "\n", 0)
if kids′ === kids
kids′ = kids[1:(open_idx - 1)]
end
any_kid_changed = true
push!(kids′, kid)
accept_node!(ctx, kid)
elseif kind(first_leaf(kid)) === K"Whitespace"
grandkid = first_leaf(kid)
grandkid = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), span(grandkid) + 1)
replace_bytes!(ctx, "\n", 0)
kid = replace_first_leaf(kid, grandkid)
if kids′ === kids
kids′ = kids[1:(open_idx - 1)]
end
any_kid_changed = true
push!(kids′, kid)
accept_node!(ctx, kid)
else
nlws = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), 1)
replace_bytes!(ctx, "\n", 0)
if kids′ === kids
kids′ = kids[1:(open_idx - 1)]
end
any_kid_changed = true
push!(kids′, nlws)
accept_node!(ctx, nlws)
push!(kids′, kid)
accept_node!(ctx, kid)
end
end
# Bring all kids between the opening and closing token to the new list
for i in (open_idx + 2):(close_idx - 2)
kid = kids[i]
any_kid_changed && push!(kids′, kid)
accept_node!(ctx, kid)
end
# Kid just before the closing token should be a newline and it should be tagged with
# pre-dedent.
if close_idx - 1 == open_idx + 1
# Just a single kid which should then have both leading and trailing newline
if any_kid_changed
# Modify this kid again by popping from the list and backtrack the stream
kid = pop!(kids′)
seek(ctx.fmt_io, position(ctx.fmt_io) - span(kid))
end
else
kid = kids[close_idx - 1]
end
if (kind(kid) === K"NewlineWs" && has_tag(kid, TAG_PRE_DEDENT)) ||
(kind(last_leaf(kid)) === K"NewlineWs" && has_tag(last_leaf(kid), TAG_PRE_DEDENT))
# Newline or newlinde hidden in first item with tag
any_kid_changed && push!(kids′, kid)
accept_node!(ctx, kid)
elseif kind(kid) === K"NewlineWs"
# Newline without tag
@assert !has_tag(kid, TAG_PRE_DEDENT)
kid = add_tag(kid, TAG_PRE_DEDENT)
if kids′ === kids
kids′ = kids[1:(close_idx - 2)]
end
any_kid_changed = true
push!(kids′, kid)
accept_node!(ctx, kid)
elseif kind(last_leaf(kid)) === K"NewlineWs"
# @assert false # Testcase?
# Hidden newline without tag
grandkid = last_leaf(kid)
@assert !has_tag(grandkid, TAG_PRE_DEDENT)
grandkid = add_tag(grandkid, TAG_PRE_DEDENT)
kid = replace_last_leaf(kid, grandkid)
if kids′ === kids
kids′ = kids[1:(close_idx - 2)]
end
any_kid_changed = true
push!(kids′, kid)
accept_node!(ctx, kid)
else
# Need to insert a newline. Note that we tag the new newline directly since it
# is the responsibility of this function (otherwise there would just be an extra
# repetitive call to add it anyway).
if kind(kid) === K"Whitespace"
# Merge with the whitespace
kid = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), span(kid) + 1)
kid = add_tag(kid, TAG_PRE_DEDENT)
replace_bytes!(ctx, "\n", 0)
if kids′ === kids
kids′ = kids[1:(open_idx - 1)]
end
any_kid_changed = true
push!(kids′, kid)
accept_node!(ctx, kid)
elseif kind(last_leaf(kid)) === K"Whitespace"
# TODO: Testcase? Need to merge here.
@assert false
else
# Note that this is a trailing newline and should be put after this item
if kids′ === kids
kids′ = kids[1:(open_idx - 1)]
end
any_kid_changed = true
push!(kids′, kid)
accept_node!(ctx, kid)
nlws = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), 1)
nlws = add_tag(nlws, TAG_PRE_DEDENT)
replace_bytes!(ctx, "\n", 0)
push!(kids′, nlws)
accept_node!(ctx, nlws)
end
end
# Closing token dedents
kid = kids[close_idx]
@assert is_leaf(kid)
if !has_tag(kid, TAG_DEDENT)
kid = add_tag(kid, TAG_DEDENT)
if kids′ === kids
kids′ = kids[1:(close_idx - 1)]
end
any_kid_changed = true
end
any_kid_changed && push!(kids′, kid)
accept_node!(ctx, kid)
# Keep any remaining kids
for i in (close_idx + 1):length(kids)
kid = kids[i]
any_kid_changed && push!(kids′, kid)
accept_node!(ctx, kid)
end
# Reset stream
seek(ctx.fmt_io, pos)
# Make a new node and return
return any_kid_changed ? make_node(node, kids′) : nothing
end
# Mark opening and closing parentheses, in a call or a tuple, with indent and dedent tags. # Mark opening and closing parentheses, in a call or a tuple, with indent and dedent tags.
function indent_paren(ctx::Context, node::Node) function indent_paren(ctx::Context, node::Node)
@ -1540,7 +1757,7 @@ function indent_paren(ctx::Context, node::Node)
kids = verified_kids(node) kids = verified_kids(node)
opening_paren_idx = findfirst(x -> kind(x) === K"(", kids)::Int opening_paren_idx = findfirst(x -> kind(x) === K"(", kids)::Int
closing_paren_idx = findnext(x -> kind(x) === K")", kids, opening_paren_idx + 1)::Int closing_paren_idx = findnext(x -> kind(x) === K")", kids, opening_paren_idx + 1)::Int
return indent_newlines_between_indices(ctx, node, opening_paren_idx, closing_paren_idx) return indent_listlike(ctx, node, opening_paren_idx, closing_paren_idx)
end end
function indent_braces(ctx::Context, node::Node) function indent_braces(ctx::Context, node::Node)
@ -1548,7 +1765,7 @@ function indent_braces(ctx::Context, node::Node)
kids = verified_kids(node) kids = verified_kids(node)
opening_brace_idx = findfirst(x -> kind(x) === K"{", kids)::Int opening_brace_idx = findfirst(x -> kind(x) === K"{", kids)::Int
closing_brace_idx = findnext(x -> kind(x) === K"}", kids, opening_brace_idx + 1)::Int closing_brace_idx = findnext(x -> kind(x) === K"}", kids, opening_brace_idx + 1)::Int
return indent_newlines_between_indices(ctx, node, opening_brace_idx, closing_brace_idx) return indent_listlike(ctx, node, opening_brace_idx, closing_brace_idx)
end end
# Insert line-continuation nodes instead of bumping the indent level. # Insert line-continuation nodes instead of bumping the indent level.
@ -1587,8 +1804,8 @@ end
function indent_tuple(ctx::Context, node::Node) function indent_tuple(ctx::Context, node::Node)
@assert kind(node) === K"tuple" @assert kind(node) === K"tuple"
kids = verified_kids(node) kids = verified_kids(node)
# Check whether this is an explicit tuple, e.g. `(a, b)`, # Check whether this is an explicit tuple, e.g. `(a, b)`, or an implicit tuple,
# or an implicit tuple, e.g. `a, b`. # e.g. `a, b`. Implicit tuples only show up in do-blocks(?).
opening_paren_idx = findfirst(x -> kind(x) === K"(", kids) opening_paren_idx = findfirst(x -> kind(x) === K"(", kids)
if opening_paren_idx === nothing if opening_paren_idx === nothing
# Implicit tuple: don't indent the closing token # Implicit tuple: don't indent the closing token
@ -1598,17 +1815,14 @@ function indent_tuple(ctx::Context, node::Node)
return nothing return nothing
end end
last_item_idx = findlast(!JuliaSyntax.is_whitespace, kids)::Int last_item_idx = findlast(!JuliaSyntax.is_whitespace, kids)::Int
return indent_newlines_between_indices( # TODO: Closing token indent?
ctx, node, first_item_idx, last_item_idx; indent_closing_token = true, return indent_listlike(ctx, node, first_item_idx, last_item_idx)
)
else else
# Explicit tuple: indent the closing token # Explicit tuple: indent the closing token
closing_paren_idx = findnext(x -> kind(x) === K")", kids, opening_paren_idx + 1)::Int closing_paren_idx = findnext(x -> kind(x) === K")", kids, opening_paren_idx + 1)::Int
@assert opening_paren_idx == firstindex(kids) @assert opening_paren_idx == firstindex(kids)
@assert closing_paren_idx == lastindex(kids) @assert closing_paren_idx == lastindex(kids)
return indent_newlines_between_indices( return indent_listlike(ctx, node, opening_paren_idx, closing_paren_idx)
ctx, node, opening_paren_idx, closing_paren_idx; indent_closing_token = false,
)
end end
end end
@ -1617,14 +1831,15 @@ function indent_parens(ctx::Context, node::Node)
return indent_paren(ctx, node) return indent_paren(ctx, node)
end end
# TODO: This is not needed? NamedTuples?
function indent_parameters(ctx::Context, node::Node) function indent_parameters(ctx::Context, node::Node)
kids = verified_kids(node) # kids = verified_kids(node)
# TODO: This is always here? # # TODO: This is always here?
semicolon_idx = findfirst(x -> kind(x) === K";", kids)::Int # semicolon_idx = findfirst(x -> kind(x) === K";", kids)::Int
last_non_ws_idx = findlast(!JuliaSyntax.is_whitespace, kids)::Int # last_non_ws_idx = findlast(!JuliaSyntax.is_whitespace, kids)::Int
return indent_newlines_between_indices( # return indent_newlines_between_indices(
ctx, node, semicolon_idx, last_non_ws_idx; indent_closing_token = true, # ctx, node, semicolon_idx, last_non_ws_idx; indent_closing_token = true,
) # )
end end
function indent_struct(ctx::Context, node::Node) function indent_struct(ctx::Context, node::Node)
@ -1710,7 +1925,7 @@ function indent_paren_block(ctx::Context, node::Node)
kids = verified_kids(node) kids = verified_kids(node)
opening_paren_idx = findfirst(x -> kind(x) === K"(", kids)::Int opening_paren_idx = findfirst(x -> kind(x) === K"(", kids)::Int
closing_paren_idx = findnext(x -> kind(x) === K")", kids, opening_paren_idx + 1)::Int closing_paren_idx = findnext(x -> kind(x) === K")", kids, opening_paren_idx + 1)::Int
return indent_newlines_between_indices(ctx, node, opening_paren_idx, closing_paren_idx) return indent_listlike(ctx, node, opening_paren_idx, closing_paren_idx)
end end
function indent_do(ctx::Context, node::Node) function indent_do(ctx::Context, node::Node)
@ -1770,14 +1985,13 @@ function indent_array(ctx::Context, node::Node)
kids = verified_kids(node) kids = verified_kids(node)
opening_bracket_idx = findfirst(x -> kind(x) === K"[", kids)::Int opening_bracket_idx = findfirst(x -> kind(x) === K"[", kids)::Int
closing_bracket_idx = findnext(x -> kind(x) === K"]", kids, opening_bracket_idx + 1)::Int closing_bracket_idx = findnext(x -> kind(x) === K"]", kids, opening_bracket_idx + 1)::Int
return indent_newlines_between_indices( return indent_listlike(ctx, node, opening_bracket_idx, closing_bracket_idx)
ctx, node, opening_bracket_idx, closing_bracket_idx,
)
end end
# TODO: can a row be multiline?
function indent_array_row(ctx::Context, node::Node) function indent_array_row(ctx::Context, node::Node)
@assert kind(node) === K"row" # @assert kind(node) === K"row"
return continue_all_newlines(ctx, node) # return continue_all_newlines(ctx, node)
end end
function indent_comparison(ctx::Context, node::Node) function indent_comparison(ctx::Context, node::Node)

156
test/runtests.jl

@ -196,51 +196,53 @@ end
# Edgecase where the NewlineWs ends up inside the second call in a chain # Edgecase where the NewlineWs ends up inside the second call in a chain
@test format_string("$(sp)a$(sp)\\$(sp)b$(sp)$(sp)\n$(sp)c$(sp)\\$(sp)d$(sp)") == @test format_string("$(sp)a$(sp)\\$(sp)b$(sp)$(sp)\n$(sp)c$(sp)\\$(sp)d$(sp)") ==
"$(sp)a \\ b ≈\n c \\ d$(sp)" "$(sp)a \\ b ≈\n c \\ d$(sp)"
# Edgecase with call-call-newline as a leading sequence
@test format_string("(\na$(sp)*$(sp)b$(sp)=>$(sp)c,\n)") == "(\n a * b => c,\n)"
end end
end end
@testset "spaces in lists" begin @testset "spaces in lists" begin
for sp in ("", " ", " "), a in ("a", "a + a", "a(x)"), b in ("b", "b + b", "b(y)") for sp in ("", " ", " "), a in ("a", "a + a", "a(x)"), b in ("b", "b + b", "b(y)")
# tuple, call, dotcall # tuple, call, dotcall, vect, ref
for f in ("", "f", "f.") for (o, c) in (("(", ")"), ("f(", ")"), ("f.(", ")"), ("[", "]"), ("T[", "]"))
# single line # single line
@test format_string("$(f)($(sp))") == "$(f)()" @test format_string("$(o)$(sp)$(c)") == "$(o)$(c)"
@test format_string("$(f)($(sp)$(a)$(sp),$(sp)$(b)$(sp))") == @test format_string("$(o)$(sp)$(a)$(sp),$(sp)$(b)$(sp)$(c)") ==
format_string("$(f)($(sp)$(a)$(sp),$(sp)$(b)$(sp),$(sp))") == format_string("$(o)$(sp)$(a)$(sp),$(sp)$(b)$(sp),$(sp)$(c)") ==
"$(f)($(a), $(b))" "$(o)$(a), $(b)$(c)"
# comments on the same line # comments on the same line
@test format_string("$(f)($(sp)$(a)$(sp), #==#$(sp)$(b)$(sp))") == @test format_string("$(o)$(sp)$(a)$(sp), #==#$(sp)$(b)$(sp)$(c)") ==
"$(f)($(a), #==# $(b))" "$(o)$(a), #==# $(b)$(c)"
@test format_string("$(f)($(sp)$(a) #==#,$(sp)$(b)$(sp))") == @test format_string("$(o)$(sp)$(a) #==#,$(sp)$(b)$(sp)$(c)") ==
"$(f)($(a) #==#, $(b))" "$(o)$(a) #==#, $(b)$(c)"
# line break in between items # line break in between items
@test format_string("$(f)($(sp)$(a)$(sp),\n$(sp)$(b)$(sp))") == @test format_string("$(o)$(sp)$(a)$(sp),\n$(sp)$(b)$(sp)$(c)") ==
format_string("$(f)($(sp)$(a)$(sp),\n$(sp)$(b)$(sp),$(sp))") == format_string("$(o)$(sp)$(a)$(sp),\n$(sp)$(b)$(sp),$(sp)$(c)") ==
"$(f)($(a),\n $(b))" "$(o)\n $(a),\n $(b),\n$(c)"
# line break after opening token # line break after opening token
@test format_string("$(f)(\n$(sp)$(a)$(sp),$(sp)$(b)$(sp))") == @test format_string("$(o)\n$(sp)$(a)$(sp),$(sp)$(b)$(sp)$(c)") ==
format_string("$(f)(\n$(sp)$(a)$(sp),$(sp)$(b)$(sp),)") == format_string("$(o)\n$(sp)$(a)$(sp),$(sp)$(b)$(sp),$(c)") ==
"$(f)(\n $(a), $(b))" "$(o)\n $(a), $(b),\n$(c)"
# line break before closing token # line break before closing token
@test format_string("$(f)($(sp)$(a)$(sp),$(sp)$(b)\n)") == @test format_string("$(o)$(sp)$(a)$(sp),$(sp)$(b)\n$(c)") ==
format_string("$(f)($(sp)$(a)$(sp),$(sp)$(b),\n)") == format_string("$(o)$(sp)$(a)$(sp),$(sp)$(b),\n$(c)") ==
"$(f)($(a), $(b),\n)" "$(o)\n $(a), $(b),\n$(c)"
# line break after opening and before closing token # line break after opening and before closing token
@test format_string("$(f)(\n$(sp)$(a)$(sp),$(sp)$(b)\n)") == @test format_string("$(o)\n$(sp)$(a)$(sp),$(sp)$(b)\n$(c)") ==
format_string("$(f)(\n$(sp)$(a)$(sp),$(sp)$(b),\n)") == format_string("$(o)\n$(sp)$(a)$(sp),$(sp)$(b),\n$(c)") ==
"$(f)(\n $(a), $(b),\n)" "$(o)\n $(a), $(b),\n$(c)"
# line break after opening and before closing token and between items # line break after opening and before closing token and between items
@test format_string("$(f)(\n$(sp)$(a)$(sp),\n$(sp)$(b)\n)") == @test format_string("$(o)\n$(sp)$(a)$(sp),\n$(sp)$(b)\n$(c)") ==
format_string("$(f)(\n$(sp)$(a)$(sp),\n$(sp)$(b),\n)") == format_string("$(o)\n$(sp)$(a)$(sp),\n$(sp)$(b),\n$(c)") ==
"$(f)(\n $(a),\n $(b),\n)" "$(o)\n $(a),\n $(b),\n$(c)"
# trailing comments # trailing comments
@test format_string("$(f)($(sp)# x\n$(sp)$(a)$(sp),$(sp)# a\n$(sp)$(b)$(sp)# b\n)") == @test format_string("$(o)$(sp)# x\n$(sp)$(a)$(sp),$(sp)# a\n$(sp)$(b)$(sp)# b\n$(c)") ==
format_string("$(f)($(sp)# x\n$(sp)$(a)$(sp),$(sp)# a\n$(sp)$(b),$(sp)# b\n)") == format_string("$(o)$(sp)# x\n$(sp)$(a)$(sp),$(sp)# a\n$(sp)$(b),$(sp)# b\n$(c)") ==
"$(f)($(sp)# x\n $(a),$(sp)# a\n $(b),$(sp)# b\n)" "$(o)\n # x\n $(a),$(sp)# a\n $(b),$(sp)# b\n$(c)"
# comments on separate lines between items # comments on separate lines between items
@test format_string("$(f)(\n# a\n$(a)$(sp),\n# b\n$(b)\n)") == @test format_string("$(o)\n# a\n$(a)$(sp),\n# b\n$(b)\n$(c)") ==
format_string("$(f)(\n# a\n$(a)$(sp),\n# b\n$(b)$(sp),\n)") == format_string("$(o)\n# a\n$(a)$(sp),\n# b\n$(b)$(sp),\n$(c)") ==
"$(f)(\n # a\n $(a),\n # b\n $(b),\n)" "$(o)\n # a\n $(a),\n # b\n $(b),\n$(c)"
end end
# Single item # Single item
@test format_string("($(sp)$(a)$(sp),$(sp))") == "($(a),)" @test format_string("($(sp)$(a)$(sp),$(sp))") == "($(a),)"
@ -443,9 +445,8 @@ end
# if-elseif-elseif-else-end # if-elseif-elseif-else-end
@test format_string( @test format_string(
"if a\n$(sp)x\n$(sp)elseif b\n$(sp)y\n$(sp)elseif " * "if a\n$(sp)x\n$(sp)elseif b\n$(sp)y\n$(sp)elseif " *
"c\n$(sp)z\n$(sp)else\n$(sp)u\n$(sp)end", "c\n$(sp)z\n$(sp)else\n$(sp)u\n$(sp)end",
) == ) == "if a\n x\nelseif b\n y\nelseif c\n z\nelse\n u\nend"
"if a\n x\nelseif b\n y\nelseif c\n z\nelse\n u\nend"
# begin-end # begin-end
@test format_string("begin\n$(sp)x\n$(sp)end") == "begin\n x\nend" @test format_string("begin\n$(sp)x\n$(sp)end") == "begin\n x\nend"
# (mutable) struct # (mutable) struct
@ -526,25 +527,52 @@ end
@test format_string("\"doc\"\n$(b)module A\n\"doc\"\n$(b)module B\n$(sp)x\n$(sp)end\n$(sp)end") == @test format_string("\"doc\"\n$(b)module A\n\"doc\"\n$(b)module B\n$(sp)x\n$(sp)end\n$(sp)end") ==
"\"doc\"\n$(b)module A\n\"doc\"\n$(b)module B\n x\nend\nend" "\"doc\"\n$(b)module A\n\"doc\"\n$(b)module B\n x\nend\nend"
end end
end
end
@testset "continuation/soft indentation" begin
for sp in ("", " ", " ", " ")
# tuple # tuple
@test format_string("(a,\n$(sp)b)") == "(a,\n b)" @test format_string("(a,\n$(sp)b)") == "(\n a,\n b,\n)"
@test format_string("(a,\n$(sp)b\n$(sp))") == @test format_string("(a,\n$(sp)b\n$(sp))") ==
format_string("(a,\n$(sp)b,\n$(sp))") == "(a,\n b,\n)" format_string("(a,\n$(sp)b,\n$(sp))") == "(\n a,\n b,\n)"
@test format_string("(\n$(sp)a,\n$(sp)b,\n$(sp))") == "(\n a,\n b,\n)" @test format_string("(\n$(sp)a,\n$(sp)b,\n$(sp))") == "(\n a,\n b,\n)"
# call, dotcall # call, dotcall
for sep in (",", ";"), d in ("", ".") for sep in (",", ";"), d in ("", ".")
@test format_string("f$(d)(a$(sep)\n$(sp)b)") == "f$(d)(a$(sep)\n b)" @test format_string("f$(d)(a$(sep)\n$(sp)b)") == "f$(d)(\n a$(sep)\n b,\n)"
@test format_string("f$(d)(a$(sep)\n$(sp)b\n$(sp))") == @test format_string("f$(d)(a$(sep)\n$(sp)b\n$(sp))") ==
format_string("f$(d)(a$(sep)\n$(sp)b,\n$(sp))") == format_string("f$(d)(a$(sep)\n$(sp)b,\n$(sp))") ==
"f$(d)(a$(sep)\n b,\n)" "f$(d)(\n a$(sep)\n b,\n)"
@test format_string("f$(d)(\n$(sp)a$(sep)\n$(sp)b,\n$(sp))") == @test format_string("f$(d)(\n$(sp)a$(sep)\n$(sp)b,\n$(sp))") ==
"f$(d)(\n a$(sep)\n b,\n)" "f$(d)(\n a$(sep)\n b,\n)"
end end
# paren-quote
@test format_string(":(a,\n$(sp)b)") == ":(\n a,\n b,\n)"
@test format_string(":(a,\n$(sp)b)") == ":(\n a,\n b,\n)"
@test format_string(":(a;\n$(sp)b)") == ":(\n a;\n b\n)"
# paren-block
@test format_string("(a;\n$(sp)b)") == "(\n a;\n b\n)"
# curly, braces, bracescat
for x in ("", "X")
tr = x == "" ? "" : ","
@test format_string("$(x){a,\n$(sp)b}") ==
format_string("$(x){a,\n$(sp)b\n$(sp)}") ==
format_string("$(x){a,\n$(sp)b,\n$(sp)}") ==
format_string("$(x){\n$(sp)a,\n$(sp)b\n$(sp)}") ==
format_string("$(x){\n$(sp)a,\n$(sp)b,\n$(sp)}") ==
"$(x){\n a,\n b,\n}"
@test format_string("$(x){a;\n$(sp)b\n$(sp)}") ==
format_string("$(x){\n$(sp)a;\n$(sp)b\n$(sp)}") ==
"$(x){\n a;\n b$(tr)\n}"
end
# array literals
for t in ("", "T")
@test format_string("$(t)[a,\n$(sp)b]") == "$(t)[\n a,\n b,\n]"
@test format_string("$(t)[\n$(sp)a,\n$(sp)b\n$(sp)]") == "$(t)[\n a,\n b,\n]"
@test format_string("$(t)[a b\n$(sp)c d]") == "$(t)[\n a b\n c d\n]"
@test format_string("$(t)[\n$(sp)a b\n$(sp)c d\n$(sp)]") == "$(t)[\n a b\n c d\n]"
end
end
end
@testset "continuation/soft indentation" begin
for sp in ("", " ", " ", " ")
# op-call, dot-op-call # op-call, dot-op-call
for d in ("", ".") for d in ("", ".")
@test format_string("a $(d)+\n$(sp)b") == "a $(d)+\n b" @test format_string("a $(d)+\n$(sp)b") == "a $(d)+\n b"
@ -573,38 +601,9 @@ end
@test format_string("a ?\n$(sp)b :\n$(sp)c") == "a ?\n b :\n c" @test format_string("a ?\n$(sp)b :\n$(sp)c") == "a ?\n b :\n c"
@test format_string("a ?\n$(sp)b :\n$(sp)c ?\n$(sp)d : e") == @test format_string("a ?\n$(sp)b :\n$(sp)c ?\n$(sp)d : e") ==
"a ?\n b :\n c ?\n d : e" "a ?\n b :\n c ?\n d : e"
# paren-quote
@test format_string(":(a,\n$(sp)b)") == ":(a,\n b)"
@test format_string(":(a,\n$(sp)b)") == ":(a,\n b)"
@test format_string(":(a;\n$(sp)b)") == ":(a;\n b)"
# paren-block
@test format_string("(a;\n$(sp)b)") == "(a;\n b)"
# array literals
for t in ("", "T")
@test format_string("$(t)[a,\n$(sp)b]") == "$(t)[a,\n b]"
@test format_string("$(t)[\n$(sp)a,\n$(sp)b\n$(sp)]") == "$(t)[\n a,\n b\n]"
@test format_string("$(t)[a b\n$(sp)c d]") == "$(t)[a b\n c d]"
@test format_string("$(t)[\n$(sp)a b\n$(sp)c d\n$(sp)]") == "$(t)[\n a b\n c d\n]"
end
# comparison # comparison
@test format_string("a == b ==\n$(sp)c") == "a == b ==\n c" @test format_string("a == b ==\n$(sp)c") == "a == b ==\n c"
@test format_string("a <= b >=\n$(sp)c") == "a <= b >=\n c" @test format_string("a <= b >=\n$(sp)c") == "a <= b >=\n c"
# curly, braces, bracescat
for x in ("", "X")
tr = x == "" ? "" : ","
@test format_string("$(x){a,\n$(sp)b}") == "$(x){a,\n b}"
@test format_string("$(x){a,\n$(sp)b\n$(sp)}") ==
format_string("$(x){a,\n$(sp)b,\n$(sp)}") ==
"$(x){a,\n b,\n}"
@test format_string("$(x){a;\n$(sp)b\n$(sp)}") == "$(x){a;\n b$(tr)\n}"
@test format_string("$(x){\n$(sp)a,\n$(sp)b\n$(sp)}") ==
format_string("$(x){\n$(sp)a,\n$(sp)b,\n$(sp)}") ==
"$(x){\n a,\n b,\n}"
@test format_string("$(x){\n$(sp)a;\n$(sp)b\n$(sp)}") ==
"$(x){\n a;\n b$(tr)\n}"
end
end end
end end
@ -623,3 +622,16 @@ end
# the node # the node
Runic.format_string("i in a + b:c") == "i in (a + b):c" Runic.format_string("i in a + b:c") == "i in (a + b):c"
end end
@testset "leading and trailing newline in multiline listlike" begin
for (o, c) in (("f(", ")"), ("(", ")"), ("{", "}"))
@test format_string("$(o)a,\nb$(c)") ==
format_string("$(o)\na,\nb$(c)") ==
format_string("$(o)\na,\nb\n$(c)") ==
"$(o)\n a,\n b,\n$(c)"
@test format_string("$(o)a, # a\nb$(c)") ==
format_string("$(o)\na, # a\nb$(c)") ==
format_string("$(o)\na, # a\nb\n$(c)") ==
"$(o)\n a, # a\n b,\n$(c)"
end
end

Loading…
Cancel
Save