Skip to content

Commit 322a3fb

Browse files
authored
fix comments in array literals (#1115)
Fixes #1113 Fixes #1116 claude did this, and i will probably merge this after reviewing it because it's a really, really ugly issue and it should be fixed, but in reality this is a gigantic hack. the right fix would be to do one or both of the following 1) restructure the CST so that the comments are placed at the top level of the vcat rather than inside the row. 2) completely rework the handling of line comments. right now they're not present in the FST and must be added back later via a notcode mechanism which means lots of moving parts that all interact with one another.
1 parent c116b5f commit 322a3fb

4 files changed

Lines changed: 116 additions & 4 deletions

File tree

HISTORY_v2.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# v2.8.1
2+
3+
Fixed a bug causing line comments inside array literals to be dropped or otherwise cause non-idempotent formatting. (#1113, #1115, #1116)
4+
15
# v2.8.0
26

37
Fixed a bug causing lack of idempotence in typed comprehension expressions (i.e., things like `T[expr for x in y]`). (#1105, #1106)

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "JuliaFormatter"
22
uuid = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
3-
version = "2.8.0"
3+
version = "2.8.1"
44
authors = ["Dominique Luna <dluna132@gmail.com> and contributors"]
55

66
[workspace]

src/styles/default/pretty.jl

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,8 @@ end
10151015
# `#= ... =#` comments are reported as whitespace by JuliaSyntax, so block
10161016
# handlers that skip whitespace would otherwise drop them. Append the comment
10171017
# (keeping it on the current line when it trails code) instead of losing it.
1018+
#
1019+
# Returns true if the comment was added, false otherwise.
10181020
function add_hasheq_comment!(t::FST, n::FST, s::State)
10191021
n.typ === HASHEQCOMMENT || return false
10201022
tnodes = t.nodes::Vector{FST}
@@ -3698,6 +3700,7 @@ function p_hcat(
36983700
# to decide whether to place boundary newlines. In other words, we can safely insert
36993701
# Placeholder nodes at these positions.
37003702

3703+
prev_comment_was_dropped = false
37013704
for (i, a) in enumerate(childs)
37023705
n = pretty(style, a, s, ctx, lineage)
37033706
if kind(a) === K"["
@@ -3728,7 +3731,7 @@ function p_hcat(
37283731
add_node!(t, n, s; join_lines = true)
37293732
elseif JuliaSyntax.is_whitespace(a)
37303733
if kind(a) === K"Comment"
3731-
add_node!(t, n, s; join_lines = true)
3734+
prev_comment_was_dropped = !add_hasheq_comment!(t, n, s)
37323735
elseif is_newline_after_2semicolons(cst, i)
37333736
# See above: we cannot convert ';;\n' to ';;'
37343737
add_node!(t, Newline(; nest_behavior = AlwaysNest), s)
@@ -3738,13 +3741,36 @@ function p_hcat(
37383741
# later on, `n_tuple!` will insert newlines after the `[` and before the
37393742
# `].`
37403743
t.nest_behavior = AlwaysNest
3741-
elseif !(kind(cst[i+1]) in KSet"; ]") && !(kind(cst[i-1]) in KSet"; [")
3744+
prev_comment_was_dropped = false
3745+
elseif !(kind(cst[i+1]) in KSet"; ] Comment") && !(kind(cst[i-1]) in KSet"; [")
37423746
# Whitespace is generally important to retain, because it can be a separator
37433747
# -- but we should omit it in a few cases:
37443748
# 1. If it's followed by / before a ';;' separator, since it's not needed.
37453749
# 2. Directly after the opening bracket.
37463750
# 3. Directly before the closing bracket.
3747-
add_node!(t, Whitespace(1), s)
3751+
#
3752+
# There's also some special logic for comments, because the current way that
3753+
# JuliaFormatter deals with comments is crazily hacky.
3754+
if !isnothing(first_arg_idx) &&
3755+
i < first_arg_idx &&
3756+
prev_comment_was_dropped
3757+
# Before the first argument, after a dropped regular # comment.
3758+
# Suppress whitespace so NOTCODE gap detection can reconstruct
3759+
# the comment at the parent level.
3760+
elseif prev_comment_was_dropped
3761+
# After a dropped regular # comment, past the first argument.
3762+
# Remove the preceding forced NEWLINE (e.g. from ;;) so that
3763+
# add_node!'s gap detection is not short-circuited by
3764+
# is_prev_newline, and can insert NOTCODE for the comment.
3765+
if is_prev_newline(t)
3766+
remove_prev_newline!(t)
3767+
end
3768+
else
3769+
add_node!(t, Whitespace(1), s)
3770+
end
3771+
prev_comment_was_dropped = false
3772+
else
3773+
prev_comment_was_dropped = false
37483774
end
37493775
elseif i == 1 && kind(a) !== K"["
37503776
# Type preceding the `[`, e.g. `T` in `T[1 2 3]`.
@@ -3862,6 +3888,24 @@ function is_semantically_important_newline(
38623888
n = length(children(row_cst))
38633889
# Start of the row - not important
38643890
i == 1 && return false
3891+
# A newline is not important if it's adjacent to a comment and all the children
3892+
# between the comment and the boundary of the row (start or end) are whitespace.
3893+
#
3894+
# Trailing comment case: all children after this newline are whitespace, and at least
3895+
# one is a comment. Example: `3 4\n # bar\n` — the newline before `# bar` is not a row
3896+
# separator. We don't need to include it.
3897+
if all(j -> JuliaSyntax.is_whitespace(row_cst[j]), (i+1):n) &&
3898+
any(j -> kind(row_cst[j]) === K"Comment", (i+1):n)
3899+
return false
3900+
end
3901+
# Leading comment case: all children before this newline are whitespace, and at least
3902+
# one is a comment. Example: `\n # foo\n 1 2` — the newline after `# foo` is not a row
3903+
# separator. We can drop all newlines after the comment, because the insertion of the
3904+
# newline after the comment is handled by the NOTCODE mechanism later on.
3905+
if all(j -> JuliaSyntax.is_whitespace(row_cst[j]), 1:(i-1)) &&
3906+
any(j -> kind(row_cst[j]) === K"Comment", 1:(i-1))
3907+
return false
3908+
end
38653909
# End of the row -- might be important
38663910
i == n && return (!is_last_arg_of_parent && kind(row_cst[i-1]) !== K";")
38673911
# Otherwise it's important

test/multidimensional_array.jl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,68 @@ end
239239
end
240240
end
241241

242+
@testset "#1113 - comments in vcat array literals" begin
243+
for s in (
244+
"X = [\n # leading\n 1 2\n 3 4\n]\n",
245+
"X = [\n 1 2\n 3 4\n # trailing\n]\n",
246+
"X = [\n # foo\n 1 2\n 3 4\n # bar\n]\n",
247+
"X = [\n 1 2\n # between\n 3 4\n]\n",
248+
"X = Int[\n # foo\n 1 2\n 3 4\n # bar\n]\n",
249+
)
250+
for style in ALL_STYLES
251+
if style isa DefaultStyle
252+
# Default should leave it unchanged
253+
test_format(s, s; ast = true)
254+
else
255+
test_format(s, nothing, style; ast = true)
256+
end
257+
end
258+
end
259+
end
260+
261+
@testset "#1113 - comments in ncat array literals" begin
262+
for s in (
263+
"[\n # leading\n 1\n 2;;\n 3\n 4\n]\n",
264+
"[\n 1\n 2;;\n 3\n 4\n # trailing\n]\n",
265+
"[\n 1\n 2;;\n # between\n 3\n 4\n]\n",
266+
"[\n 1;\n 2;;\n # between\n 3;\n 4\n]\n",
267+
)
268+
for style in ALL_STYLES
269+
if style isa DefaultStyle
270+
# Default should leave it unchanged
271+
test_format(s, s; ast = true)
272+
else
273+
test_format(s, nothing, style; ast = true)
274+
end
275+
end
276+
end
277+
278+
# this one is still a bit wonky because the \n after 2;; is collapsed
279+
s = "[\n # leading\n 1\n 2;;\n 3\n 4;;;\n 5\n 6;;\n 7\n 8\n]\n"
280+
for style in ALL_STYLES
281+
test_format(s, nothing, style; ast = true)
282+
@test occursin("# leading", format_text(s))
283+
end
284+
@test_broken format_text(s) == s
285+
end
286+
287+
@testset "#1113 - comments in hcat array literals" begin
288+
for s in (
289+
"X = [\n # only\n 1 2\n]\n",
290+
"X = [\n 1 2\n # trailing\n]\n",
291+
"X = [\n # foo\n 1 2\n # bar\n]\n",
292+
"X = [\n 1 2;;\n # hi\n 3 4\n]\n",
293+
"X = [#= inline =# 1 2]\n",
294+
"X = [1 #= mid =# 2]\n",
295+
)
296+
for style in ALL_STYLES
297+
if style isa DefaultStyle
298+
test_format(s, s; ast = true)
299+
else
300+
test_format(s, nothing, style; ast = true)
301+
end
302+
end
303+
end
304+
end
305+
242306
end # module

0 commit comments

Comments
 (0)