@@ -35,17 +35,15 @@ defmodule Styler.Style.Blocks do
3535 # case statement with exactly 2 `->` cases
3636 # rewrite to `if` if it's any of 3 trivial cases
3737 def run ( { { :case , m , [ head , [ { _ , [ { :-> , am , [ [ lhs_a ] , a ] } , { :-> , bm , [ [ lhs_b ] , b ] } ] } ] ] } , _ } = zipper , ctx ) do
38- end_line = m [ :end ] [ :line ]
39-
40- ctx =
41- ctx
42- |> Map . update! ( :comments , & pull_leading_comment ( & 1 , am [ :line ] , body_start_line ( a ) ) )
43- |> Map . update! ( :comments , & pull_leading_comment ( & 1 , bm [ :line ] , body_start_line ( b ) ) )
38+ # @TODO shouldn't be shifting if we aren't doing if_ast rewrites.
39+ # try to put shift into the if_ast header that matches transformations?
40+ ctx = shift_arrow_comments_into_body ( ctx , { am , a } , { bm , b } )
41+ b = Macro . update_meta ( b , & Keyword . put ( & 1 , :end_of_expression , [ line: m [ :end ] [ :line ] , newlines: 1 ] ) )
4442
4543 case { lhs_a , lhs_b } do
46- { { _ , _ , [ true ] } , { _ , _ , [ false ] } } -> if_ast ( zipper , head , a , b , ctx , else: end_line )
47- { { _ , _ , [ true ] } , { :_ , _ , _ } } -> if_ast ( zipper , head , a , b , ctx , else: end_line )
48- { { _ , _ , [ false ] } , { _ , _ , [ true ] } } -> if_ast ( zipper , head , b , a , ctx , do: end_line , else: bm [ :line ] )
44+ { { _ , _ , [ true ] } , { _ , _ , [ false ] } } -> if_ast ( zipper , head , a , b , ctx )
45+ { { _ , _ , [ true ] } , { :_ , _ , _ } } -> if_ast ( zipper , head , a , b , ctx )
46+ { { _ , _ , [ false ] } , { _ , _ , [ true ] } } -> if_ast ( zipper , head , b , a , ctx )
4947 _ -> { :cont , zipper , ctx }
5048 end
5149 end
@@ -85,7 +83,7 @@ defmodule Styler.Style.Blocks do
8583 { :cont , zipper , ctx }
8684 end
8785
88- def run ( { { :cond , m , [ [ { do_ , clauses } ] ] } , _ } = zipper , ctx ) do
86+ def run ( { { :cond , _ , [ [ { do_ , clauses } ] ] } , _ } = zipper , ctx ) do
8987 # ensure all final `atom -> final_clause` use `true` for consistency.
9088 # `:else` is cute but consistency is all.
9189 rewrite_literal_to_true = fn
@@ -106,12 +104,10 @@ defmodule Styler.Style.Blocks do
106104 # `b` (the final clause, going into `else`) trails right up to this `cond`'s own `end` - use that
107105 # real boundary so a dangling/trailing comment moves along with its content instead of getting stranded.
108106 [ { :-> , am , [ [ head ] , a ] } , { :-> , bm , [ [ { :__block__ , _ , [ true ] } ] , b ] } ] ->
109- comments =
110- ctx . comments
111- |> pull_leading_comment ( am [ :line ] , body_start_line ( a ) )
112- |> pull_leading_comment ( bm [ :line ] , body_start_line ( b ) )
113-
114- if_ast ( zipper , head , a , b , % { ctx | comments: comments } , else: m [ :end ] [ :line ] )
107+ ctx = shift_arrow_comments_into_body ( ctx , { am , a } , { bm , b } )
108+ # TODO needs a test for danglers in cond do
109+ # b = Macro.update_meta(b, &Keyword.put(&1, :end_of_expression, [line: m[:end][:line], newlines: 1]))
110+ if_ast ( zipper , head , a , b , ctx )
115111
116112 clauses ->
117113 { :cont , Zipper . replace_children ( zipper , [ [ { do_ , clauses } ] ] ) , ctx }
@@ -207,6 +203,11 @@ defmodule Styler.Style.Blocks do
207203 # Credo.Check.Refactor.NegatedConditionsWithElse
208204 # if !x, do: y, else: z => if x, do: z, else: y
209205 [ negator , [ { do_ , do_body } , { else_ , else_body } ] ] when is_negator ( negator ) ->
206+ # end of expression hacks ensure that these bodies keep dangling comments in their blocks.
207+ # someday we might find a better way!
208+ # ohhhhhhh probably i need to fix the line numbers on the do and else to match the lines.... HMM
209+ do_body = Macro . update_meta ( do_body , & Keyword . put ( & 1 , :end_of_expression , [ line: Style . meta ( else_ ) [ :line ] , newlines: 1 ] ) )
210+ else_body = Macro . update_meta ( else_body , & Keyword . put ( & 1 , :end_of_expression , [ line: m [ :end ] [ :line ] , newlines: 1 ] ) )
210211 zipper |> Zipper . replace ( { :if , m , [ invert ( negator ) , [ { do_ , else_body } , { else_ , do_body } ] ] } ) |> run ( ctx )
211212
212213 # drop `else end`
@@ -217,15 +218,9 @@ defmodule Styler.Style.Blocks do
217218 [ head , [ do_block , { _ , { :__block__ , _ , [ nil ] } } ] ] ->
218219 { :cont , Zipper . replace ( zipper , { :if , m , [ head , [ do_block ] ] } ) , ctx }
219220
220- [ head , [ do_ , { else_kw , _ } = else_ ] ] ->
221+ [ head , [ do_ , else_ ] ] ->
221222 if Style . max_line ( do_ ) > Style . max_line ( else_ ) do
222- # we inverted the if/else blocks of this `if` statement in a previous pass (due to negators or unless)
223- # shift comments etc to make it happy now. `do_`'s content used to be paired with `else_kw` and always
224- # trailed right up to this `if`'s own `end`; `else_`'s content used to be paired with `do_kw`, and
225- # `else_kw`'s real (unmodified) line still marks exactly where that content used to trail off to -
226- # use those real boundaries so a dangling/trailing comment moves along with its content instead of
227- # getting stranded.
228- if_ast ( zipper , head , do_ , else_ , ctx , do: m [ :end ] [ :line ] , else: Style . meta ( else_kw ) [ :line ] )
223+ if_ast ( zipper , head , do_ , else_ , ctx )
229224 else
230225 { :cont , zipper , ctx }
231226 end
@@ -387,52 +382,45 @@ defmodule Styler.Style.Blocks do
387382
388383 defp nodes_equivalent? ( a , b ) , do: Style . without_meta ( a ) == Style . without_meta ( b )
389384
390- defp body_start_line ( { :__block__ , meta , [ child | _ ] } ) , do: meta [ :line ] || Style . meta ( child ) [ :line ]
391- defp body_start_line ( { _ , meta , _ } ) , do: meta [ :line ]
392-
393- # A leading comment on a `case`/`cond` clause's own `->` line (eg `# a` directly above `false ->`) can sit
394- # a line or more above the clause body's own content once that body is multi-line - too far for the
395- # (unwidened, on purpose - see `bound_trailing`) adjacency check in `order_line_meta_and_comments` to find.
396- # Pull any such leading comment down to sit directly adjacent to the body's real first line instead, so
397- # normal adjacency finds it. Leaves the body's own `:line` (and thus its rendered position) untouched.
398- defp pull_leading_comment ( comments , header_line , body_line ) do
399- { mine , rest } = Style . comments_for_lines ( comments , header_line , header_line )
400- delta = body_line - header_line
401- if delta == 0 , do: comments , else: Enum . sort_by ( rest ++ Enum . map ( mine , & % { & 1 | line: & 1 . line + delta } ) , & & 1 . line )
385+ # shifts comments sitting directly on arrows into the body.
386+ # ideally this gets rolled into comment management, but because we're removing the arrows, we can't see the gaps
387+ # in the bodies of these things.
388+ # maybe an alternative is to hack the bodies to have a start line equal to the arrow's start line, thereby
389+ # leaving all comment manip to our comment manip function
390+ # yeah, the main problem seems to be that the body of an arrow can have a line number much higher than the arrow itself, and so we lose comments modifying the arrow.
391+ # we need to encode that arrow line number somehow. essentially, that's the line number of our `do` keyword or whatever
392+ defp shift_arrow_comments_into_body ( ctx , { am , a } , { bm , b } ) do
393+ ctx
394+ |> Map . update! ( :comments , & do_shift_arrow_comments_into_body ( & 1 , am , a ) )
395+ |> Map . update! ( :comments , & do_shift_arrow_comments_into_body ( & 1 , bm , b ) )
396+ end
397+
398+ defp do_shift_arrow_comments_into_body ( comments , arrow_meta , body ) do
399+ arrow_line = arrow_meta [ :line ]
400+ body_line = Style . first_line ( body )
401+
402+ if body_line == arrow_line do
403+ comments
404+ else
405+ { mine , rest } = Style . comments_for_lines ( comments , arrow_line , arrow_line )
406+ mine = Enum . map ( mine , & % { & 1 | line: & 1 . line + 1 } )
407+ Enum . sort_by ( rest ++ mine , & & 1 . line )
408+ end
402409 end
403410
404411 # When we're coming in from here, we know we're coming in for a transformation from a different block
405- defp if_ast ( zipper , head , { _ , _ , _ } = do_body , { _ , _ , _ } = else_body , ctx , bounds ) do
412+ defp if_ast ( zipper , head , { _ , _ , _ } = do_body , { _ , _ , _ } = else_body , ctx ) do
406413 do_ = { { :__block__ , [ line: nil ] , [ :do ] } , do_body }
407414 else_ = { { :__block__ , [ line: nil ] , [ :else ] } , else_body }
408- if_ast ( zipper , head , do_ , else_ , ctx , bounds )
415+ if_ast ( zipper , head , do_ , else_ , ctx )
409416 end
410417
411- defp if_ast ( zipper , { _ , meta , _ } = head , { do_kw , do_body } , { else_kw , else_body } , ctx , bounds ) do
418+ defp if_ast ( zipper , { _ , meta , _ } = head , { do_kw , do_body } , { else_kw , else_body } , ctx ) do
412419 line = meta [ :line ]
413420
414- # When converting different blocks to if statements, the comment algo can miss dangling comments for different shapes.
415- # Setting a fake end_of_expression helps clue it in to grab those comments.
416- # this is a dirty hack - the end_of_expression gets nixed before this function returns
417- [ do_body , else_body ] =
418- for { kw , { node , meta , children } } <- [ do: do_body , else: else_body ] do
419- meta =
420- if line = bounds [ kw ] ,
421- do: Keyword . put ( meta , :end_of_expression , [ newlines: 1 , line: line - 1 ] ) ,
422- else: Keyword . delete ( meta , :end_of_expression )
423-
424- meta =
425- if meta [ :line ] ,
426- do: meta ,
427- else: Keyword . put ( meta , :line , Style . meta ( hd ( children ) ) [ :line ] )
428-
429- { node , meta , children }
430- end
431-
432421 { [ do_body , else_body ] , comments } = Style . order_line_meta_and_comments ( [ do_body , else_body ] , ctx . comments , line )
433422
434- # the lines for the else and end keywords. not sure why the bounds...
435- else_line = Style . max_line ( do_body ) + if ( bounds [ :do ] , do: 1 , else: 0 )
423+ else_line = Style . max_line ( do_body )
436424 end_line = Style . max_line ( else_body ) + 1
437425 # clean up the dangling comments hack
438426 do_body = Macro . update_meta ( do_body , & Keyword . delete ( & 1 , :end_of_expression ) )
0 commit comments