From 15598cc64c0d715ce217e5045dca2d2e9bd9cde9 Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Sun, 23 Jun 2024 01:50:15 +0200 Subject: [PATCH] Insert parens around op calls in colon, fixes #3. --- src/Runic.jl | 1 + src/chisels.jl | 2 +- src/runestone.jl | 110 ++++++++++++++++++++++++++++++++++++----------- test/runtests.jl | 18 +++++++- 4 files changed, 104 insertions(+), 27 deletions(-) diff --git a/src/Runic.jl b/src/Runic.jl index 35f9aec..e99117b 100644 --- a/src/Runic.jl +++ b/src/Runic.jl @@ -305,6 +305,7 @@ function format_node!(ctx::Context, node::Node)::Union{Node, Nothing, NullNode} @return_something spaces_around_anonymous_function(ctx, node) @return_something spaces_around_keywords(ctx, node) @return_something no_spaces_around_colon_etc(ctx, node) + @return_something parens_around_op_calls_in_colon(ctx, node) @return_something for_loop_use_in(ctx, node) @return_something braces_around_where_rhs(ctx, node) @return_something four_space_indent(ctx, node) diff --git a/src/chisels.jl b/src/chisels.jl index e7efea6..82b9bcd 100644 --- a/src/chisels.jl +++ b/src/chisels.jl @@ -258,7 +258,7 @@ function has_newline_after_non_whitespace(node::Node) # Everything is whitespace... return any(x -> kind(x) === K"NewlineWs", kids) end - return any(x -> kind(x) === K"NewlineWs", kids[idx + 1:end]) || + return any(x -> kind(x) === K"NewlineWs", kids[(idx + 1):end]) || has_newline_after_non_whitespace(kids[idx]) # if idx === nothing # # All is whitespace, check if any of the kids is a newline diff --git a/src/runestone.jl b/src/runestone.jl index e49d757..5762653 100644 --- a/src/runestone.jl +++ b/src/runestone.jl @@ -187,7 +187,7 @@ function spaces_around_x(ctx::Context, node::Node, is_x::F, n_leaves_per_x::Int # Whitespace node but replace since not single space any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, ws) replace_bytes!(ctx, " ", span(kid)) @@ -213,7 +213,7 @@ function spaces_around_x(ctx::Context, node::Node, is_x::F, n_leaves_per_x::Int accept_node!(ctx, kid′) any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, kid′) end @@ -230,7 +230,7 @@ function spaces_around_x(ctx::Context, node::Node, is_x::F, n_leaves_per_x::Int # Not a whitespace node, insert one any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, ws) replace_bytes!(ctx, " ", 0) @@ -334,7 +334,7 @@ function spaces_in_listlike(ctx::Context, node::Node) n_items = count( x -> !(JuliaSyntax.is_whitespace(x) || kind(x) === K","), - @view(kids[opening_leaf_idx + 1:closing_leaf_idx - 1]), + @view(kids[(opening_leaf_idx + 1):(closing_leaf_idx - 1)]), ) last_item_idx = findprev(x -> !(JuliaSyntax.is_whitespace(x) || kind(x) in KSet", ;"), kids, closing_leaf_idx - 1) if last_item_idx <= opening_leaf_idx @@ -358,7 +358,7 @@ function spaces_in_listlike(ctx::Context, node::Node) require_trailing_comma = has_tag(node, TAG_TRAILING_COMMA) elseif n_items > 0 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]) end expect_trailing_comma = require_trailing_comma @@ -397,7 +397,7 @@ function spaces_in_listlike(ctx::Context, node::Node) replace_bytes!(ctx, "", span(kid′)) this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end elseif kind(kid′) === K"NewlineWs" || (kind(kid′) === K"Whitespace" && peek(i) === K"Comment") @@ -426,7 +426,7 @@ function spaces_in_listlike(ctx::Context, node::Node) any_kid_changed |= this_kid_changed if any_kid_changed if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, kid′) end @@ -452,7 +452,7 @@ function spaces_in_listlike(ctx::Context, node::Node) # (no state change) this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, "", span(kid′)) elseif kind(kid′) === K"NewlineWs" || @@ -463,7 +463,7 @@ function spaces_in_listlike(ctx::Context, node::Node) # - there is a comma coming but it is on the next line (weird) # - there is a comment with no space before it next_non_ws_idx = 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, ) next_kind = next_non_ws_idx === nothing ? nothing : kind(kids[next_non_ws_idx]) # Insert a comma if there isn't one coming @@ -471,7 +471,7 @@ function spaces_in_listlike(ctx::Context, node::Node) @assert expect_trailing_comma this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, ",", 0) push!(kids′, comma) @@ -486,7 +486,7 @@ function spaces_in_listlike(ctx::Context, node::Node) @assert kind(node) in KSet"call dotcall curly" # TODO: Can this happen for named tuples? @assert i === last_item_idx @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, ) === nothing if kind(first_leaf(kid′)) === K"Whitespace" # Delete the whitespace leaf @@ -511,7 +511,7 @@ function spaces_in_listlike(ctx::Context, node::Node) accept_node!(ctx, kid′) if any_kid_changed if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, kid′) end @@ -531,7 +531,7 @@ function spaces_in_listlike(ctx::Context, node::Node) # Wrong span, replace it this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, " ", span(kid′)) accept_node!(ctx, ws) @@ -568,7 +568,7 @@ function spaces_in_listlike(ctx::Context, node::Node) kid′ = replace_first_leaf(kid′, ws) this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, " ", span(ws_node)) accept_node!(ctx, kid′) @@ -579,7 +579,7 @@ function spaces_in_listlike(ctx::Context, node::Node) # Insert a standalone space kid and then accept the current node this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, " ", 0) push!(kids′, ws) @@ -598,7 +598,7 @@ function spaces_in_listlike(ctx::Context, node::Node) # removed this_kid_changed = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, "", span(kid′)) elseif kind(kid′) === K"NewlineWs" || @@ -620,7 +620,7 @@ function spaces_in_listlike(ctx::Context, node::Node) @assert expect_trailing_comma any_kid_changed = true if kids′ === kids - kids′ = kids[1:closing_leaf_idx - 1] + kids′ = kids[1:(closing_leaf_idx - 1)] end replace_bytes!(ctx, ",", 0) push!(kids′, comma) @@ -714,7 +714,7 @@ function no_spaces_around_x(ctx::Context, node::Node, is_x::F) where {F} # Ignore it but need to copy kids and re-write bytes any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, "", span(kid)) else @@ -741,7 +741,7 @@ function no_spaces_around_x(ctx::Context, node::Node, is_x::F) where {F} end if any_changes if kids === kids′ - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, kid) end @@ -847,7 +847,7 @@ function spaces_around_keywords(ctx::Context, node::Node) # Replace with single space. any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end replace_bytes!(ctx, " ", span(kid)) push!(kids′, ws) @@ -864,7 +864,7 @@ function spaces_around_keywords(ctx::Context, node::Node) accept_node!(ctx, kid′) any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end push!(kids′, kid′) end @@ -875,7 +875,7 @@ function spaces_around_keywords(ctx::Context, node::Node) @assert kind(node) === K"where" any_changes = true if kids′ === kids - kids′ = kids[1:i - 1] + kids′ = kids[1:(i - 1)] end # Insert the space before/after the kid depending on whether we are looking # for a space before or after a keyword @@ -1040,8 +1040,8 @@ function braces_around_where_rhs(ctx::Context, node::Node) return nothing end # Wrap the rhs in a braces node - kids′ = kids[1:rhs_idx - 1] - for i in 1:rhs_idx - 1 + kids′ = kids[1:(rhs_idx - 1)] + for i in 1:(rhs_idx - 1) accept_node!(ctx, kids[i]) end opening_brace = Node(JuliaSyntax.SyntaxHead(K"{", 0), 1) @@ -1058,7 +1058,7 @@ function braces_around_where_rhs(ctx::Context, node::Node) replace_bytes!(ctx, "}", 0) accept_node!(ctx, closing_brace) # Accept any remaining kids - for i in rhs_idx + 1:length(kids) + for i in (rhs_idx + 1):length(kids) accept_node!(ctx, kids[i]) push!(kids′, kids[i]) end @@ -1067,6 +1067,66 @@ function braces_around_where_rhs(ctx::Context, node::Node) return make_node(node, kids′) end +function parens_around_op_calls_in_colon(ctx::Context, node::Node) + if !(is_infix_op_call(node) && kind(infix_op_call_op(node)) === K":") + return nothing + end + + kids = verified_kids(node) + kids′ = kids + any_changes = false + pos = position(ctx.fmt_io) + + for i in eachindex(kids) + kid = kids[i] + if is_infix_op_call(kid) + if kids′ === kids + kids′ = kids[1:(i - 1)] + end + grandkids = verified_kids(kid) + first_non_ws = findfirst(!JuliaSyntax.is_whitespace, grandkids)::Int + last_non_ws = findlast(!JuliaSyntax.is_whitespace, grandkids)::Int + # Extract whitespace grandkids to become kids + for j in 1:(first_non_ws - 1) + accept_node!(ctx, grandkids[j]) + push!(kids′, grandkids[j]) + end + # Create the parens node + opening_paren = Node(JuliaSyntax.SyntaxHead(K"(", 0), 1) + replace_bytes!(ctx, "(", 0) + accept_node!(ctx, opening_paren) + parens_kids = [opening_paren] + kid′_kids = grandkids[first_non_ws:last_non_ws] + kid′ = make_node(kid, kid′_kids) + accept_node!(ctx, kid′) + push!(parens_kids, kid′) + closing_paren = Node(JuliaSyntax.SyntaxHead(K")", 0), 1) + replace_bytes!(ctx, ")", 0) + accept_node!(ctx, closing_paren) + push!(parens_kids, closing_paren) + parens = Node(JuliaSyntax.SyntaxHead(K"parens", 0), parens_kids) + push!(kids′, parens) + for j in (last_non_ws + 1):length(grandkids) + accept_node!(ctx, grandkids[j]) + push!(kids′, grandkids[j]) + end + any_changes = true + else + accept_node!(ctx, kid) + any_changes && push!(kids′, kid) + end + end + # Reset stream + seek(ctx.fmt_io, pos) + # Rebuild node and return + if any_changes + node′ = make_node(node, kids′) + return node′ + else + return nothing + end +end + # This function materialized all indentations marked by `insert_delete_mark_newlines`. function four_space_indent(ctx::Context, node::Node) kind(node) === K"NewlineWs" || return nothing diff --git a/test/runtests.jl b/test/runtests.jl index fae4f06..616eb8e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -176,7 +176,7 @@ end # Exceptions to the rule: `:` and `^` # a:b @test format_string("$(sp)a$(sp):$(sp)b$(sp)") == "$(sp)a:b$(sp)" - @test format_string("$(sp)a + a$(sp):$(sp)b + b$(sp)") == "$(sp)a + a:b + b$(sp)" + @test format_string("$(sp)a + a$(sp):$(sp)b + b$(sp)") == "$(sp)(a + a):(b + b)$(sp)" @test format_string("$(sp)(1 + 2)$(sp):$(sp)(1 + 3)$(sp)") == "$(sp)(1 + 2):(1 + 3)$(sp)" # a:b:c @@ -594,3 +594,19 @@ end end end + +@testset "parens around op calls in colon" begin + for a in ("a + a", "a + a * a"), sp in ("", " ", " ") + @test format_string("$(a)$(sp):$(sp)$(a)") == "($(a)):($(a))" + @test format_string("$(a)$(sp):$(sp)$(a)$(sp):$(sp)$(a)") == "($(a)):($(a)):($(a))" + @test format_string("$(a)$(sp):$(sp)$(a)$(sp):$(sp)$(a):$(a)") == "(($(a)):($(a)):($(a))):($(a))" + end + # No-ops + for p in ("", "()"), sp in ("", " ", " ") + @test format_string("a$(p)$(sp):$(sp)b$(p)") == "a$(p):b$(p)" + @test format_string("a$(p)$(sp):$(sp)b$(p)$(sp):$(sp)c$(p)") == "a$(p):b$(p):c$(p)" + end + # Edgecase: leading whitespace so that the paren have to be inserted in the middle of + # the node + Runic.format_string("i in a + b:c") == "i in (a + b):c" +end