Browse Source

Enforce leading and trailing newline in blocklike constructs (#46)

This patch introduces formatting for all blocklike constructs
(`if`/`try`/`function`/`begin`/`for`/`while`/...) such that inner block
always start and end with a newline character.

For example,
```julia
if x print("x") else print("y") end
```
will be reformatted as
```julia
if x
    print("x")
else
    print("y")
end
```

An exception is (currently) made for comments, for example
```julia
if x # comment
    print("x")
end
```
will *not* be formatted as
```julia
if x
    # comment
    print("x")
end
```
even though the comment is technically inside the block.

Closes #35.
pull/47/head
Fredrik Ekre 1 year ago committed by GitHub
parent
commit
e128bc9b77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      src/Runic.jl
  2. 149
      src/chisels.jl
  3. 431
      src/runestone.jl
  4. 153
      test/runtests.jl

4
src/Runic.jl

@ -147,6 +147,7 @@ function Context(
src_tree = Node( src_tree = Node(
JuliaSyntax.parseall(JuliaSyntax.GreenNode, src_str; ignore_warnings = true, version = v"2-"), JuliaSyntax.parseall(JuliaSyntax.GreenNode, src_str; ignore_warnings = true, version = v"2-"),
) )
normalize_tree!(src_tree)
fmt_io = IOBuffer() fmt_io = IOBuffer()
fmt_tree = nothing fmt_tree = nothing
# Set up buffers # Set up buffers
@ -356,6 +357,9 @@ function format_node_with_kids!(ctx::Context, node::Node)
# The node should be replaced with the new one. Reset the stream and try # The node should be replaced with the new one. Reset the stream and try
# again until it is accepted. # again until it is accepted.
@assert kid′′ isa Node @assert kid′′ isa Node
if !is_leaf(kid′′)
@assert span(kid′′) == mapreduce(span, +, verified_kids(kid′′); init = 0)
end
this_kid_changed = true this_kid_changed = true
seek(ctx.fmt_io, fmt_pos) seek(ctx.fmt_io, fmt_pos)
kid′ = kid′′ kid′ = kid′′

149
src/chisels.jl

@ -66,6 +66,155 @@ function stringify_flags(node::Node)
return String(take!(io)) return String(take!(io))
end end
# The parser is somewhat inconsistent(?) with where e.g. whitespace nodes end up so in order
# to simplify the formatting code we normalize some things.
function normalize_tree!(node)
is_leaf(node) && return
kids = verified_kids(node)
# Move standalone K"NewlineWs" (and other??) nodes from between the var block and the
# body block in K"let" nodes.
# Note that this happens before the whitespace into block normalization below because
# for let we want to move it to the subsequent block instead.
if kind(node) === K"let"
varsidx = findfirst(x -> kind(x) === K"block", kids)::Int
bodyidx = findnext(x -> kind(x) === K"block", kids, varsidx + 1)::Int
r = (varsidx + 1):(bodyidx - 1)
if length(r) > 0
items = kids[r]
deleteat!(kids, r)
bodyidx -= length(r)
body = kids[bodyidx]
prepend!(verified_kids(body), items)
kids[bodyidx] = make_node(body, verified_kids(body))
end
end
# Normalize K"Whitespace" nodes in blocks. For example in `if x y end` the space will be
# outside the block just before the K"end" node, but in `if x\ny\nend` the K"NewlineWs"
# will end up inside the block.
if kind(node) in KSet"function if elseif for while try do macro module baremodule let struct module"
blockidx = findfirst(x -> kind(x) === K"block", kids)
while blockidx !== nothing && blockidx < length(kids)
if kind(kids[blockidx + 1]) !== K"Whitespace"
# TODO: This repeats the computation below...
blockidx = findnext(x -> kind(x) === K"block", kids, blockidx + 1)
continue
end
# Pop the ws and push it into the block instead
block = kids[blockidx]
blockkids = verified_kids(block)
@assert !(kind(blockkids[end]) in KSet"Whitespace NewlineWs")
push!(blockkids, popat!(kids, blockidx + 1))
# Remake the block to recompute the span
kids[blockidx] = make_node(block, blockkids)
# Find next block
blockidx = findnext(x -> kind(x) === K"block", kids, blockidx + 1)
end
end
# Normalize K"Whitespace" nodes in if-elseif-else chains where the node needs to move
# many steps into the last else block...
if kind(node) === K"if"
elseifidx = findfirst(x -> kind(x) === K"elseif", kids)
if elseifidx !== nothing
endidx = findnext(x -> kind(x) === K"end", kids, elseifidx + 1)::Int
if elseifidx + 2 == endidx && kind(kids[elseifidx + 1]) === K"Whitespace"
# Pop the ws and push it into the last block instead
ws = popat!(kids, elseifidx + 1)
elseifnode = insert_into_last_else_block(kids[elseifidx], ws)
@assert elseifnode !== nothing
kids[elseifidx] = elseifnode
end
end
end
# Normalize K"Whitespace" nodes in try-catch-finally-else
if kind(node) === K"try"
catchidx = findfirst(x -> kind(x) in KSet"catch finally else", kids)
while catchidx !== nothing
if kind(kids[catchidx + 1]) === K"Whitespace"
ws = popat!(kids, catchidx + 1)
catchnode = insert_into_last_catchlike_block(kids[catchidx], ws)
@assert catchnode !== nothing
kids[catchidx] = catchnode
end
catchidx = findnext(x -> kind(x) in KSet"catch finally else", kids, catchidx + 1)
end
end
# Normalize K"NewlineWs" nodes in empty do-blocks
if kind(node) === K"do"
tupleidx = findfirst(x -> kind(x) === K"tuple", kids)::Int
blockidx = findnext(x -> kind(x) === K"block", kids, tupleidx + 1)::Int
@assert tupleidx + 1 == blockidx
tuple = kids[tupleidx]
tuplekids = verified_kids(tuple)
if kind(tuplekids[end]) === K"NewlineWs"
# If the tuple ends with a K"NewlineWs" node we move it into the block
block = kids[blockidx]
blockkids = verified_kids(block)
@assert kind(blockkids[1]) !== K"Whitespace"
pushfirst!(blockkids, pop!(tuplekids))
# Remake the nodes to recompute the spans
kids[tupleidx] = make_node(tuple, tuplekids)
kids[blockidx] = make_node(block, blockkids)
end
end
@assert kids === verified_kids(node)
for kid in kids
ksp = span(kid)
normalize_tree!(kid)
@assert span(kid) == ksp
end
# We only move around things inside this node so the span should be unchanged
@assert span(node) == mapreduce(span, +, kids; init = 0)
return node
end
function insert_into_last_else_block(node, ws)
@assert kind(node) === K"elseif"
kids = verified_kids(node)
elseifidx = findfirst(x -> !is_leaf(x) && kind(x) === K"elseif", kids)
if elseifidx !== nothing
@assert elseifidx == lastindex(kids)
elseifnode′ = insert_into_last_else_block(kids[elseifidx], ws)
@assert elseifnode′ !== nothing
kids[elseifidx] = elseifnode′
return make_node(node, kids)
end
# Find the else block
elseifblockidx = findfirst(x -> kind(x) === K"block", kids)::Int
elseleafidx = findnext(x -> kind(x) === K"else", kids, elseifblockidx + 1)::Int
elseblockidx = findnext(x -> kind(x) === K"block", kids, elseleafidx + 1)::Int
@assert elseblockidx == lastindex(kids)
elseblock = kids[elseblockidx]
# Insert the node
elseblockkids = verified_kids(elseblock)
@assert !(kind(elseblockkids[end]) in KSet"NewlineWs Whitespace")
push!(elseblockkids, ws)
# Remake the else block
kids[elseblockidx] = make_node(elseblock, elseblockkids)
# Remake and return the elseif node
return make_node(node, kids)
end
function insert_into_last_catchlike_block(node, ws)
@assert kind(node) in KSet"catch finally else"
kids = verified_kids(node)
catchblockidx = findfirst(x -> kind(x) === K"block", kids)::Int
@assert catchblockidx == lastindex(kids)
catchblock = kids[catchblockidx]
catchblockkids = verified_kids(catchblock)
@assert !(kind(catchblockkids[end]) in KSet"NewlineWs Whitespace")
push!(catchblockkids, ws)
# Remake the catch block
kids[catchblockidx] = make_node(catchblock, catchblockkids)
# Remake and return the catch node
return make_node(node, kids)
end
# Node tags # # Node tags #

431
src/runestone.jl

@ -1733,16 +1733,22 @@ function indent_function_or_macro(ctx::Context, node::Node)
kids[end_idx] = add_tag(end_node, TAG_DEDENT) kids[end_idx] = add_tag(end_node, TAG_DEDENT)
any_kid_changed = true any_kid_changed = true
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
# K"tuple" when this is an anonymous function # K"tuple" when this is an anonymous function
@assert !is_leaf(sig_node) && kind(sig_node) in KSet"call where :: tuple parens" @assert !is_leaf(sig_node) && kind(sig_node) in KSet"call where :: tuple parens"
# Fourth node is the function/macro body block. # Fourth node is the function/macro body block.
block_idx = sig_idx + 1 block_idx = sig_idx + 1
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# Fifth node is the closing end keyword # Fifth node is the closing end keyword
end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1)::Int end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1)::Int
@ -1753,7 +1759,7 @@ function indent_function_or_macro(ctx::Context, node::Node)
any_kid_changed = true any_kid_changed = true
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_let(ctx::Context, node::Node) function indent_let(ctx::Context, node::Node)
@ -1780,12 +1786,18 @@ function indent_let(ctx::Context, node::Node)
# @assert is_leaf(ln_node) && kind(ln_node) === K"NewlineWs" # @assert is_leaf(ln_node) && kind(ln_node) === K"NewlineWs"
# Fourth node is the function body block. # Fourth node is the function body block.
block_idx = findnext(x -> kind(x) === K"block", kids, vars_idx + 1)::Int block_idx = findnext(x -> kind(x) === K"block", kids, vars_idx + 1)::Int
block_node = kids[block_idx] let p = position(ctx.fmt_io)
@assert !is_leaf(block_node) && kind(block_node) === K"block" for i in 1:(block_idx - 1)
block_node′ = indent_block(ctx, block_node) accept_node!(ctx, kids[i])
if block_node′ !== nothing end
kids[block_idx] = block_node′ block_node = kids[block_idx]
any_kid_changed = true @assert !is_leaf(block_node) && kind(block_node) === K"block"
block_node′ = indent_block(ctx, block_node)
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# Look for the end node # Look for the end node
end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1)::Int end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1)::Int
@ -1795,14 +1807,21 @@ function indent_let(ctx::Context, node::Node)
any_kid_changed = true any_kid_changed = true
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
# TODO: Reuse indent_block? # TODO: Reuse indent_block?
function indent_begin(ctx::Context, node::Node, block_kind = K"begin") function indent_begin(ctx::Context, node::Node, block_kind = K"begin")
@assert kind(node) === K"block"
pos = position(ctx.fmt_io)
node′ = indent_block(ctx, node)
if node′ !== nothing
node = node′
any_kid_changed = false
end
kids = verified_kids(node) kids = verified_kids(node)
any_kid_changed = false any_kid_changed = false
# First node is the begin keyword # First node is the begin/quote keyword
begin_idx = 1 begin_idx = 1
begin_node = kids[begin_idx] begin_node = kids[begin_idx]
@assert is_leaf(begin_node) && kind(begin_node) === block_kind @assert is_leaf(begin_node) && kind(begin_node) === block_kind
@ -1834,33 +1853,153 @@ function indent_begin(ctx::Context, node::Node, block_kind = K"begin")
any_kid_changed = true any_kid_changed = true
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing # Reset stream
seek(ctx.fmt_io, pos)
return any_kid_changed ? make_node(node, kids) : nothing
end end
# TODO: This needs to be reworked to handle non-standard cases like, for example, one-liners function indent_block(
# of the form `if x y end`. For now we only handle the standard case and ignore the rest. ctx::Context, node::Node; allow_empty::Bool = true, do_indent::Bool = true,
function indent_block(::Context, node::Node) )
@assert kind(node) === K"block" && !is_leaf(node) @assert kind(node) === K"block" && !is_leaf(node)
@assert !JuliaSyntax.has_flags(node, JuliaSyntax.PARENS_FLAG)
kids = verified_kids(node) kids = verified_kids(node)
pos = position(ctx.fmt_io)
any_kid_changed = false any_kid_changed = false
# Expect a NewlineWs node at the end of the block (otherwise the closing `end` is not on
# a separate line). # begin-end and quote-end have their respective keywords inside the block...
trailing_idx = findlast(x -> kind(x) === K"NewlineWs", kids) is_begin_end = length(kids) > 2 && kind(kids[1]) in KSet"begin quote" &&
if trailing_idx === nothing || trailing_idx != lastindex(kids) kind(kids[end]) === K"end"
begin # TODO: let-block if is_begin_end is boxed
function make_view(x)
if is_begin_end
return @view(x[2:(end - 1)])
else
return @view(x[:])
end
end
function popatview!(x, idx)
local p = parent(x)
if is_begin_end
item = popat!(p, idx + 1)
else
item = popat!(p, idx)
end
return make_view(p), item
end
function popview!(x)
return popatview!(x, lastindex(x))
end
function insertview!(x, idx, item)
local p = parent(x)
if is_begin_end
insert!(p, idx + 1, item)
else
insert!(p, idx, item)
end
return make_view(p)
end
function pushview!(x, item)
return insertview!(x, lastindex(x) + 1, item)
end
end
kids′ = make_view(kids)
if is_begin_end
accept_node!(ctx, kids[1])
end
# If the block is empty and contain no newlines, and empty blocks are allowed, we just
# return
if allow_empty && findfirst(!JuliaSyntax.is_whitespace, kids′) === nothing &&
findfirst(x -> kind(x) === K"NewlineWs", kids′) === nothing
return nothing return nothing
elseif !has_tag(kids[trailing_idx], TAG_PRE_DEDENT) end
kids[trailing_idx] = add_tag(kids[trailing_idx], TAG_PRE_DEDENT)
# Ensure a NewlineWs node at the end of the block (otherwise the closing
# `end/else/catch/...` is not on a separate line).
trailing_idx = findlast(x -> kind(x) === K"NewlineWs", kids′)
if trailing_idx === nothing || trailing_idx != lastindex(kids′)
# Missing NewlineWs node, insert.
kids′ = make_view(copy(kids))
p = position(ctx.fmt_io)
for k in kids′
accept_node!(ctx, k)
end
# If the previous node is a K"Whitespace" node we just overwrite it instead of
# merging becuase this whitespace will end up as trailing/leading whitespace anyway.
if length(kids′) > 0 && kind(kids′[end]) === K"Whitespace"
spn = span(kids′[end])
seek(ctx.fmt_io, position(ctx.fmt_io) - spn)
replace_bytes!(ctx, "", spn)
kids′, _ = popview!(kids′)
end
# Insert a NewlineWs node in the tree and stream
replace_bytes!(ctx, "\n", 0)
k = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), 1)
if do_indent
k = add_tag(k, TAG_PRE_DEDENT)
end
kids′ = pushview!(kids′, k)
seek(ctx.fmt_io, p)
any_kid_changed = true
elseif do_indent && !has_tag(kids′[trailing_idx], TAG_PRE_DEDENT)
kids′ = make_view(copy(kids))
kids′[trailing_idx] = add_tag(kids′[trailing_idx], TAG_PRE_DEDENT)
any_kid_changed = true any_kid_changed = true
end end
# Look for a leading NewlineWs node trailing_idx = findlast(x -> kind(x) === K"NewlineWs", kids′)::Int
leading_idx = findfirst(x -> kind(x) === K"NewlineWs", kids) @assert trailing_idx == lastindex(kids′)
if leading_idx !== nothing && leading_idx < trailing_idx
# TODO: Forgot why we check for this. I think it is only necessary if we want to # Ensure a NewlineWs node at the beginning of the block (otherwise the opening
# split a one-liner into multiple lines. # `begin/try/...` is not on a separate line).
# return nothing # Note: Currently a block is allowed to have space + comment before the newline to
# support trailing comments on the same line as the keyword, e.g.
# ```
# let x = 1 # comment
# y = x + 1
# end
# ```
# TODO: Perhaps only certain blocks should allow this? E.g. `let` to support comments
# for the variables (the last comment would end up inside the block)?
leading_idx = findfirst(x -> kind(x) === K"NewlineWs", kids′)::Int
if !(
leading_idx == 1 ||
(leading_idx == 2 && kind(kids′[1]) === K"Comment") ||
(leading_idx == 3 && kind(kids′[1]) === K"Whitespace" && kind(kids′[2]) === K"Comment")
)
# Allow a comment on the same line
insert_idx = 1
if length(kids′) > 1 && kind(kids′[1]) === K"Comment"
insert_idx = 2
elseif length(kids′) > 2 && kind(kids′[1]) === K"Whitespace" && kind(kids′[2]) === K"Comment"
insert_idx = 3
end
if kids === parent(kids′)
kids′ = make_view(copy(kids))
end
# If the node is a Whitespace we just overwrite it with a `\n ` node.
wsspn = 0
if kind(kids′[insert_idx]) === K"Whitespace"
kids′, ws = popatview!(kids′, insert_idx)
wsspn = span(ws)
end
# If we end up in this code path we are most likely splitting a single line block
# into multiples lines. This means that we haven't yet updated the indent level for
# the keyword just before this block so in most cases we save a roundtrip by
# increasing the indent level with 1 here.
nl = "\n" * " "^(4 * (ctx.indent_level + 1))
# Skip past whitespace + comment
for i in 1:(insert_idx - 1)
accept_node!(ctx, kids′[i])
end
replace_bytes!(ctx, nl, wsspn)
k = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), sizeof(nl))
kids′ = insertview!(kids′, insert_idx, k)
any_kid_changed = true
end end
@assert verified_kids(node) === kids # Reset stream
return any_kid_changed ? node : nothing seek(ctx.fmt_io, pos)
return any_kid_changed ? make_node(node, parent(kids′)) : nothing
end end
function indent_catch(ctx::Context, node::Node) function indent_catch(ctx::Context, node::Node)
@ -1881,12 +2020,18 @@ function indent_catch(ctx::Context, node::Node)
# Skip over the catch-identifier (if any) # Skip over the catch-identifier (if any)
block_idx = findnext(x -> kind(x) === K"block", kids, catch_idx + 1)::Int block_idx = findnext(x -> kind(x) === K"block", kids, catch_idx + 1)::Int
@assert kind(kids[block_idx]) === K"block" @assert kind(kids[block_idx]) === K"block"
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_try(ctx::Context, node::Node) function indent_try(ctx::Context, node::Node)
@ -1904,26 +2049,44 @@ function indent_try(ctx::Context, node::Node)
end end
# Second node the try-block # Second node the try-block
try_block_idx = findnext(!JuliaSyntax.is_whitespace, kids, try_idx + 1)::Int try_block_idx = findnext(!JuliaSyntax.is_whitespace, kids, try_idx + 1)::Int
try_block_node′ = indent_block(ctx, kids[try_block_idx]) let p = position(ctx.fmt_io)
if try_block_node′ !== nothing for i in 1:(try_block_idx - 1)
kids[try_block_idx] = try_block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
try_block_node′ = indent_block(ctx, kids[try_block_idx])
if try_block_node′ !== nothing
kids[try_block_idx] = try_block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# Check for catch/finally. They can be in any order # Check for catch/finally. They can be in any order
catch_idx = findnext(x -> kind(x) in KSet"catch finally", kids, try_block_idx + 1)::Int catch_idx = findnext(x -> kind(x) in KSet"catch finally", kids, try_block_idx + 1)::Int
@assert !is_leaf(kids[catch_idx]) && kind(kids[catch_idx]) in KSet"catch finally" @assert !is_leaf(kids[catch_idx]) && kind(kids[catch_idx]) in KSet"catch finally"
catch_node′ = indent_catch(ctx, kids[catch_idx]) let p = position(ctx.fmt_io)
if catch_node′ !== nothing for i in 1:(catch_idx - 1)
kids[catch_idx] = catch_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
catch_node′ = indent_catch(ctx, kids[catch_idx])
if catch_node′ !== nothing
kids[catch_idx] = catch_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# There may be an else in between catch and finally (lol) # There may be an else in between catch and finally (lol)
else_idx = findnext(x -> kind(x) === K"else", kids, catch_idx + 1) else_idx = findnext(x -> kind(x) === K"else", kids, catch_idx + 1)
if else_idx !== nothing if else_idx !== nothing
else_node′ = indent_catch(ctx, kids[else_idx]) let p = position(ctx.fmt_io)
if else_node′ !== nothing for i in 1:(else_idx - 1)
kids[else_idx] = else_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
else_node′ = indent_catch(ctx, kids[else_idx])
if else_node′ !== nothing
kids[else_idx] = else_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
end end
# Check for the other one # Check for the other one
@ -1932,10 +2095,16 @@ function indent_try(ctx::Context, node::Node)
x -> kind(x) === other_kind, kids, something(else_idx, catch_idx) + 1, x -> kind(x) === other_kind, kids, something(else_idx, catch_idx) + 1,
) )
if finally_idx !== nothing if finally_idx !== nothing
finally_node′ = indent_catch(ctx, kids[finally_idx]) let p = position(ctx.fmt_io)
if finally_node′ !== nothing for i in 1:(finally_idx - 1)
kids[finally_idx] = finally_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
finally_node′ = indent_catch(ctx, kids[finally_idx])
if finally_node′ !== nothing
kids[finally_idx] = finally_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
end end
# Check for end # Check for end
@ -1948,7 +2117,7 @@ function indent_try(ctx::Context, node::Node)
any_kid_changed = true any_kid_changed = true
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_if(ctx::Context, node::Node) function indent_if(ctx::Context, node::Node)
@ -1980,19 +2149,31 @@ function indent_if(ctx::Context, node::Node)
# Fourth node is the body block. # Fourth node is the body block.
block_idx = findnext(!JuliaSyntax.is_whitespace, kids, cond_idx + 1)::Int block_idx = findnext(!JuliaSyntax.is_whitespace, kids, cond_idx + 1)::Int
@assert block_idx == cond_idx + 1 @assert block_idx == cond_idx + 1
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# Check for elseif # Check for elseif
elseif_idx = findnext(x -> kind(x) === K"elseif", kids, block_idx + 1) elseif_idx = findnext(x -> kind(x) === K"elseif", kids, block_idx + 1)
if elseif_idx !== nothing if elseif_idx !== nothing
@assert !is_leaf(kids[elseif_idx]) && kind(kids[elseif_idx]) === K"elseif" @assert !is_leaf(kids[elseif_idx]) && kind(kids[elseif_idx]) === K"elseif"
elseif_node′ = indent_if(ctx, kids[elseif_idx]) let p = position(ctx.fmt_io)
if elseif_node′ !== nothing for i in 1:(elseif_idx - 1)
kids[elseif_idx] = elseif_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
elseif_node′ = indent_if(ctx, kids[elseif_idx])
if elseif_node′ !== nothing
kids[elseif_idx] = elseif_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
end end
# Check for else # Check for else
@ -2011,10 +2192,16 @@ function indent_if(ctx::Context, node::Node)
kids[else_idx] = else_node kids[else_idx] = else_node
else_block_idx = findnext(!JuliaSyntax.is_whitespace, kids, else_idx + 1)::Int else_block_idx = findnext(!JuliaSyntax.is_whitespace, kids, else_idx + 1)::Int
@assert kind(kids[else_block_idx]) === K"block" @assert kind(kids[else_block_idx]) === K"block"
else_block′ = indent_block(ctx, kids[else_block_idx]) let p = position(ctx.fmt_io)
if else_block′ !== nothing for i in 1:(else_block_idx - 1)
kids[else_block_idx] = else_block′ accept_node!(ctx, kids[i])
any_kid_changed = true end
else_block′ = indent_block(ctx, kids[else_block_idx])
if else_block′ !== nothing
kids[else_block_idx] = else_block′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
end end
# Check for end # Check for end
@ -2028,7 +2215,7 @@ function indent_if(ctx::Context, node::Node)
end end
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_call(ctx::Context, node::Node) function indent_call(ctx::Context, node::Node)
@ -2083,7 +2270,7 @@ function indent_newlines_between_indices(
any_kid_changed |= this_kid_changed any_kid_changed |= this_kid_changed
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
# Tags opening and closing tokens for indent/dedent and the newline just before the closing # Tags opening and closing tokens for indent/dedent and the newline just before the closing
@ -2377,17 +2564,23 @@ function indent_loop(ctx::Context, node::Node)
end end
# findlast because the condition can also be a block # findlast because the condition can also be a block
block_idx = findlast(x -> kind(x) === K"block", kids)::Int block_idx = findlast(x -> kind(x) === K"block", kids)::Int
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
end_idx = findlast(x -> kind(x) === K"end", kids)::Int end_idx = findlast(x -> kind(x) === K"end", kids)::Int
if !has_tag(kids[end_idx], TAG_DEDENT) if !has_tag(kids[end_idx], TAG_DEDENT)
kids[end_idx] = add_tag(kids[end_idx], TAG_DEDENT) kids[end_idx] = add_tag(kids[end_idx], TAG_DEDENT)
any_kid_changed = true any_kid_changed = true
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_implicit_tuple(ctx::Context, node::Node) function indent_implicit_tuple(ctx::Context, node::Node)
@ -2440,17 +2633,23 @@ function indent_struct(ctx::Context, node::Node)
any_kid_changed = true any_kid_changed = true
end end
block_idx = findnext(x -> kind(x) === K"block", kids, struct_idx + 1)::Int block_idx = findnext(x -> kind(x) === K"block", kids, struct_idx + 1)::Int
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
end_idx = findlast(x -> kind(x) === K"end", kids)::Int end_idx = findlast(x -> kind(x) === K"end", kids)::Int
if !has_tag(kids[end_idx], TAG_DEDENT) if !has_tag(kids[end_idx], TAG_DEDENT)
kids[end_idx] = add_tag(kids[end_idx], TAG_DEDENT) kids[end_idx] = add_tag(kids[end_idx], TAG_DEDENT)
any_kid_changed = true any_kid_changed = true
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_short_circuit(ctx::Context, node::Node) function indent_short_circuit(ctx::Context, node::Node)
@ -2516,7 +2715,7 @@ function continue_all_newlines(
any_kid_changed = true any_kid_changed = true
end end
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
end end
@ -2628,10 +2827,16 @@ function indent_do(ctx::Context, node::Node)
end end
# Find the do body block # Find the do body block
block_idx = findnext(x -> kind(x) === K"block", kids, do_idx + 1)::Int block_idx = findnext(x -> kind(x) === K"block", kids, do_idx + 1)::Int
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx])
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# Closing `end` # Closing `end`
end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1)::Int end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1)::Int
@ -2639,7 +2844,7 @@ function indent_do(ctx::Context, node::Node)
kids[end_idx] = add_tag(kids[end_idx], TAG_DEDENT) kids[end_idx] = add_tag(kids[end_idx], TAG_DEDENT)
any_kid_changed = true any_kid_changed = true
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function indent_quote(ctx::Context, node::Node) function indent_quote(ctx::Context, node::Node)
@ -2654,12 +2859,13 @@ function indent_quote(ctx::Context, node::Node)
# `bar` in `foo.bar` is a quote block... # `bar` in `foo.bar` is a quote block...
return nothing return nothing
end end
@assert block_idx == 1 # Otherwise need to seek the stream
block_node′ = indent_begin(ctx, kids[block_idx], K"quote") block_node′ = indent_begin(ctx, kids[block_idx], K"quote")
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
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
else else
# The short form can be ignored since the inside (K"block", K"tuple", or # The short form can be ignored since the inside (K"block", K"tuple", or
# K"Identifier") of the quote will be handled by other passes. # K"Identifier") of the quote will be handled by other passes.
@ -2688,14 +2894,16 @@ function indent_comparison(ctx::Context, node::Node)
end end
# Indent a nested module # Indent a nested module
function indent_module(ctx::Context, node::Node) function indent_module(ctx::Context, node::Node; do_indent::Bool = true)
@assert kind(node) === K"module"
kids = verified_kids(node) kids = verified_kids(node)
any_kid_changed = false any_kid_changed = false
pos = position(ctx.fmt_io)
# First node is the module keyword # First node is the module keyword
mod_idx = 1 mod_idx = 1
mod_node = kids[mod_idx] mod_node = kids[mod_idx]
@assert is_leaf(mod_node) && kind(mod_node) in KSet"module baremodule" @assert is_leaf(mod_node) && kind(mod_node) in KSet"module baremodule"
if !has_tag(mod_node, TAG_INDENT) if do_indent && !has_tag(mod_node, TAG_INDENT)
kids[mod_idx] = add_tag(mod_node, TAG_INDENT) kids[mod_idx] = add_tag(mod_node, TAG_INDENT)
any_kid_changed = true any_kid_changed = true
end end
@ -2717,21 +2925,29 @@ function indent_module(ctx::Context, node::Node)
block_idx = 3 block_idx = 3
end end
# Next node is the module body block. # Next node is the module body block.
block_node′ = indent_block(ctx, kids[block_idx]) let p = position(ctx.fmt_io)
if block_node′ !== nothing for i in 1:(block_idx - 1)
kids[block_idx] = block_node′ accept_node!(ctx, kids[i])
any_kid_changed = true end
block_node′ = indent_block(ctx, kids[block_idx]; do_indent = do_indent)
if block_node′ !== nothing
kids[block_idx] = block_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
# Skip until the closing end keyword # Skip until the closing end keyword
end_idx = findnext(x -> kind(x) === K"end", kids, block_idx + 1) end_idx = findnext(x -> kind(x) === K"end", kids, 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 do_indent && !has_tag(end_node, TAG_DEDENT)
kids[end_idx] = add_tag(end_node, TAG_DEDENT) kids[end_idx] = add_tag(end_node, TAG_DEDENT)
any_kid_changed = true any_kid_changed = true
end end
@assert verified_kids(node) === kids @assert verified_kids(node) === kids
return any_kid_changed ? node : nothing # Reset the stream
seek(ctx.fmt_io, pos)
return any_kid_changed ? make_node(node, kids) : nothing
end end
# The only thing at top level that we need to indent are modules which don't occupy the full # The only thing at top level that we need to indent are modules which don't occupy the full
@ -2740,21 +2956,28 @@ function indent_toplevel(ctx::Context, node::Node)
@assert kind(node) === K"toplevel" @assert kind(node) === K"toplevel"
kids = verified_kids(node) kids = verified_kids(node)
mod_idx = findfirst(x -> kind(x) === K"module", kids) mod_idx = findfirst(x -> kind(x) === K"module", kids)
if mod_idx === nothing || count(!JuliaSyntax.is_whitespace, kids) == 1 if mod_idx === nothing
# No module or module that is the only top level expression # No module here
return nothing return nothing
end end
# If the only top level expression is a module we don't indent it
do_indent = count(!JuliaSyntax.is_whitespace, kids) > 1
any_kid_changed = false any_kid_changed = false
while mod_idx !== nothing while mod_idx !== nothing
mod_node = kids[mod_idx] let p = position(ctx.fmt_io)
mod_node′ = indent_module(ctx, mod_node) for i in 1:(mod_idx - 1)
if mod_node′ !== nothing accept_node!(ctx, kids[i])
kids[mod_idx] = mod_node′ end
any_kid_changed = true mod_node′ = indent_module(ctx, kids[mod_idx]; do_indent = do_indent)
if mod_node′ !== nothing
kids[mod_idx] = mod_node′
any_kid_changed = true
end
seek(ctx.fmt_io, p)
end end
mod_idx = findnext(x -> kind(x) === K"module", kids, mod_idx + 1) mod_idx = findnext(x -> kind(x) === K"module", kids, mod_idx + 1)
end end
return any_kid_changed ? node : nothing return any_kid_changed ? make_node(node, kids) : nothing
end end
function insert_delete_mark_newlines(ctx::Context, node::Node) function insert_delete_mark_newlines(ctx::Context, node::Node)
@ -2814,9 +3037,9 @@ function insert_delete_mark_newlines(ctx::Context, node::Node)
return indent_comparison(ctx, node) return indent_comparison(ctx, node)
elseif kind(node) === K"toplevel" elseif kind(node) === K"toplevel"
return indent_toplevel(ctx, node) return indent_toplevel(ctx, node)
elseif kind(node) === K"module" && elseif kind(node) === K"module"
findlast(x -> x === K"module", ctx.lineage_kinds) !== nothing do_indent = findlast(x -> x === K"module", ctx.lineage_kinds) !== nothing
return indent_module(ctx, node) return indent_module(ctx, node; do_indent = do_indent)
end end
return nothing return nothing
end end

153
test/runtests.jl

@ -438,7 +438,7 @@ end
@test format_string("A$(sp)where$(sp){T}$(sp)where$(sp){S}") == "A where {T} where {S}" @test format_string("A$(sp)where$(sp){T}$(sp)where$(sp){S}") == "A where {T} where {S}"
@test format_string("f()$(sp)do$(sp)x\ny\nend") == "f() do x\n y\nend" @test format_string("f()$(sp)do$(sp)x\ny\nend") == "f() do x\n y\nend"
@test format_string("f()$(sp)do\ny\nend") == "f() do\n y\nend" @test format_string("f()$(sp)do\ny\nend") == "f() do\n y\nend"
@test format_string("f()$(sp)do; y end") == "f() do; y end" @test format_string("f()$(sp)do; y end") == "f() do;\n y\nend"
# After `where` (anywhere else?) a newline can be used instead of a space # After `where` (anywhere else?) a newline can be used instead of a space
@test format_string("A$(sp)where$(sp)\n{A}") == "A where\n{A}" @test format_string("A$(sp)where$(sp)\n{A}") == "A where\n{A}"
end end
@ -503,17 +503,18 @@ end
@test format_string("function f()\n$(sp)x\n$(sp)end") == @test format_string("function f()\n$(sp)x\n$(sp)end") ==
"function f()\n x\nend" "function f()\n x\nend"
@test format_string("function f end") == "function f end" @test format_string("function f end") == "function f end"
@test_broken format_string("function f\nend") == "function f\nend" # TODO
@test format_string("function ∉ end") == "function ∉ end" @test format_string("function ∉ end") == "function ∉ end"
# macro-end # macro-end
@test format_string("macro f()\n$(sp)x\n$(sp)end") == @test format_string("macro f()\n$(sp)x\n$(sp)end") ==
"macro f()\n x\nend" "macro f()\n x\nend"
@test format_string("macro f() x end") == "macro f() x end" @test format_string("macro f() x end") == "macro f()\n x\nend"
# let-end # let-end
@test format_string("let a = 1\n$(sp)x\n$(sp)end") == "let a = 1\n x\nend" @test format_string("let a = 1\n$(sp)x\n$(sp)end") == "let a = 1\n x\nend"
@test format_string("let\n$(sp)x\n$(sp)end") == "let\n x\nend" @test format_string("let\n$(sp)x\n$(sp)end") == "let\n x\nend"
@test format_string("let a = 1 # a\n$(sp)x\n$(sp)end") == @test format_string("let a = 1 # a\n$(sp)x\n$(sp)end") ==
"let a = 1 # a\n x\nend" "let a = 1 # a\n x\nend"
@test format_string("let a = 1; x end") == "let a = 1; x end" @test format_string("let a = 1; x end") == "let a = 1\n ; x\nend"
# begin-end # begin-end
@test format_string("begin\n$(sp)x\n$(sp)end") == @test format_string("begin\n$(sp)x\n$(sp)end") ==
"begin\n x\nend" "begin\n x\nend"
@ -635,7 +636,7 @@ end
@test format_string("$(b)module \$(A)\n$(sp)x\n$(sp)end\nf") == @test format_string("$(b)module \$(A)\n$(sp)x\n$(sp)end\nf") ==
"$(b)module \$(A)\n x\nend\nf" "$(b)module \$(A)\n x\nend\nf"
# single line module # single line module
@test format_string("$(b)module A; x; end\nf") == "$(b)module A; x; end\nf" @test format_string("$(b)module A; x; end\nf") == "$(b)module A\n ; x;\nend\nf"
end end
# tuple # tuple
@test format_string("(a,\n$(sp)b)") == "(\n a,\n b,\n)" @test format_string("(a,\n$(sp)b)") == "(\n a,\n b,\n)"
@ -690,9 +691,9 @@ end
"$(t)[\n a for a in b\n]" "$(t)[\n a for a in b\n]"
end end
# Single line begin-end # Single line begin-end
@test format_string("begin x\n$(sp)end") == "begin x\nend" @test format_string("begin x\n$(sp)end") == "begin\n x\nend"
@test format_string("begin x end") == "begin x end" @test format_string("begin x end") == "begin\n x\nend"
@test format_string("begin\n x end") == "begin\n x end" @test format_string("begin\n x end") == "begin\n x\nend"
# Functors # Functors
@test format_string("function$(sp)(a::A)(b)\nx\nend") == @test format_string("function$(sp)(a::A)(b)\nx\nend") ==
"function (a::A)(b)\n x\nend" "function (a::A)(b)\n x\nend"
@ -967,6 +968,144 @@ end
end end
end end
# TODO: This can be used for `;`-trimming by a loop and delimeters
@testset "blocks start and end with newline" begin
# for/while-end
for verb in ("for", "while")
@test format_string("$(verb) x in X x end") ==
format_string("$(verb) x in X\nx end") ==
format_string("$(verb) x in X x\nend") ==
"$(verb) x in X\n x\nend"
end
# if-end
@test format_string("if a x end") == "if a\n x\nend"
# if-else-end
@test format_string("if a x else y end") == "if a\n x\nelse\n y\nend"
# if-elseif-end
@test format_string("if a x elseif b y end") == "if a\n x\nelseif b\n y\nend"
# if-elseif-elseif-end
@test format_string("if a x elseif b y elseif c z end") ==
"if a\n x\nelseif b\n y\nelseif c\n z\nend"
# if-elseif-else-end
@test format_string("if a x elseif b y else z end") ==
"if a\n x\nelseif b\n y\nelse\n z\nend"
# if-elseif-elseif-else-end
@test format_string("if a elseif b elseif c else end") ==
"if a elseif b elseif c else end"
@test_broken format_string("if a elseif b elseif c else x end") ==
"if a\nelseif b\nelseif c\nelse\n x\nend"
@test format_string("if a x elseif b y elseif c z else u end") ==
"if a\n x\nelseif b\n y\nelseif c\n z\nelse\n u\nend"
# try-catch-end
@test format_string("try x catch\ny end") == "try\n x\ncatch\n y\nend"
# try-catch(err)-end
@test format_string("try x catch err y end") == "try\n x\ncatch err\n y\nend"
# try-catch-finally-end
@test format_string("try x catch\ny finally z end") ==
"try\n x\ncatch\n y\nfinally\n z\nend"
# try-catch(err)-finally-end
@test format_string("try x catch err y finally z end") ==
"try\n x\ncatch err\n y\nfinally\n z\nend"
# try-finally-catch-end (yes, this is allowed...)
@test format_string("try x finally y catch\nz end") ==
"try\n x\nfinally\n y\ncatch\n z\nend"
# try-finally-catch(err)-end
@test format_string("try x finally y catch err z end") ==
"try\n x\nfinally\n y\ncatch err\n z\nend"
if VERSION >= v"1.8"
# try-catch-else-end
@test format_string("try x catch\ny else z end") ==
"try\n x\ncatch\n y\nelse\n z\nend"
# try-catch(err)-else-end
@test format_string("try x catch err y else z end") ==
"try\n x\ncatch err\n y\nelse\n z\nend"
# try-catch-else-finally-end
@test format_string("try x catch\ny else z finally z end") ==
"try\n x\ncatch\n y\nelse\n z\nfinally\n z\nend"
# try-catch(err)-else-finally-end
@test format_string("try x catch err y else z finally z end") ==
"try\n x\ncatch err\n y\nelse\n z\nfinally\n z\nend"
end
# do-end
@test format_string("open() do\na end") == "open() do\n a\nend"
@test format_string("open() do io a end") == "open() do io\n a\nend"
# let-end
@test format_string("let a = 1\nx end") == "let a = 1\n x\nend"
@test format_string("let\nx end") == "let\n x\nend"
@test format_string("let a = 1 # a\nx end") == "let a = 1 # a\n x\nend"
# function-end
@test format_string("function f() x end") == "function f()\n x\nend"
@test format_string("function() x end") == "function()\n x\nend"
@test format_string("function () x end") == "function ()\n x\nend"
@test format_string("function f end") == "function f end"
# macro-end
@test format_string("macro f() x end") == "macro f()\n x\nend"
# quote-end
@test format_string("quote x end") == "quote\n x\nend"
# begin-end
@test format_string("begin x end") == "begin\n x\nend"
# (mutable) struct
for mut in ("", "mutable ")
@test format_string("$(mut)struct A x end") == "$(mut)struct A\n x\nend"
end
# module-end, baremodule-end
for b in ("", "bare")
# Just a module
@test format_string("$(b)module A x end") == "$(b)module A\nx\nend"
# Comment before
@test format_string("# c\n$(b)module A x end") == "# c\n$(b)module A\nx\nend"
# Docstring before
@test format_string("\"doc\"\n$(b)module A x end") == "\"doc\"\n$(b)module A\nx\nend"
# code before
@test format_string("f\n$(b)module A x end") == "f\n$(b)module A\n x\nend"
@test format_string("f\n$(b)module A x end\n$(b)module B x end") ==
"f\n$(b)module A\n x\nend\n$(b)module B\n x\nend"
# code after
@test format_string("$(b)module A x end\nf") == "$(b)module A\n x\nend\nf"
# nested modules
@test format_string("$(b)module A $(b)module B x end end") ==
"$(b)module A\n$(b)module B\n x\nend\nend"
# nested documented modules
@test format_string("\"doc\"\n$(b)module A\n\"doc\"\n$(b)module B x end\nend") ==
"\"doc\"\n$(b)module A\n\"doc\"\n$(b)module B\n x\nend\nend"
end
# Empty blocks
for verb in ("for", "while")
@test format_string("$(verb) x in X end") == "$(verb) x in X end"
@test format_string("$(verb) x in X\nend") == "$(verb) x in X\nend"
end
@test format_string("if a end") == "if a end"
@test format_string("if a\nend") == "if a\nend"
@test format_string("if a else end") == "if a else end"
@test_broken format_string("if a x else end") == "if a\n x\nelse\nend"
@test format_string("if a elseif b end") == "if a elseif b end"
@test_broken format_string("if a x elseif b end") == "if a\n x\nelseif b\nend"
@test format_string("if a elseif b elseif c end") == "if a elseif b elseif c end"
@test_broken format_string("if a x elseif b elseif c end") ==
"if a\n x\nelseif b\nelseif c\nend"
@test format_string("if a elseif b else end") == "if a elseif b else end"
@test_broken format_string("if a x elseif b else end") == "if a\n x\nelseif b\nelse\nend"
@test format_string("if a elseif b elseif c else end") ==
"if a elseif b elseif c else end"
@test_broken format_string("if a elseif b elseif c else x end") ==
"if a\nelseif b\nelseif c\nelse\n x\nend"
@test format_string("try catch y end") == "try catch y end"
@test_broken format_string("try catch y y end") == "try\ncatch y\n y\nend"
@test format_string("open() do io end") == "open() do io end"
@test format_string("function f() end") == "function f() end"
@test format_string("macro f() end") == "macro f() end"
@test format_string("quote end") == "quote end"
@test format_string("begin end") == "begin end"
for mut in ("", "mutable ")
@test format_string("$(mut)struct A end") == "$(mut)struct A end"
end
for b in ("", "bare")
@test format_string("$(b)module A end") == "$(b)module A end"
@test format_string("$(b)module A $(b)module B end end") ==
"$(b)module A\n$(b)module B end\nend"
end
end
@testset "# runic: (on|off)" begin @testset "# runic: (on|off)" begin
for exc in ("", "!"), word in ("runic", "format") for exc in ("", "!"), word in ("runic", "format")
on = "#$(exc) $(word): on" on = "#$(exc) $(word): on"

Loading…
Cancel
Save