Skip to content

Commit fb269fa

Browse files
authored
fix: correctly order aliases (#322)
Lines were being mangled due to deletes being incorrectly applied after inserts, and newlines being messed up.
1 parent 7591304 commit fb269fa

File tree

4 files changed

+73
-34
lines changed

4 files changed

+73
-34
lines changed

apps/engine/lib/engine/code_mod/directives.ex

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ defmodule Engine.CodeMod.Directives do
5454
|> String.trim_trailing()
5555

5656
zeroed = put_in(insert_position.character, 1)
57-
new_range = Range.new(zeroed, zeroed)
57+
insert_range = Range.new(zeroed, zeroed)
5858

5959
block_text =
6060
if is_binary(trailer) do
@@ -63,41 +63,39 @@ defmodule Engine.CodeMod.Directives do
6363
block_text
6464
end
6565

66-
edits = remove_old_directives(all_ranges)
66+
delete_edits = remove_old_directives(all_ranges)
6767

68-
edits ++ [Edit.new(block_text, new_range)]
68+
case delete_edits do
69+
[] ->
70+
insert_edit = Edit.new(block_text, insert_range)
71+
[insert_edit]
72+
73+
_ ->
74+
# When deleting existing directives, the delete edits remove entire lines
75+
# including their newlines. So we need to add a trailing newline to the
76+
# inserted block to maintain proper line structure.
77+
insert_edit = Edit.new(block_text <> "\n", insert_range)
78+
79+
[insert_edit | delete_edits]
80+
end
6981
end
7082

7183
defp indent(text, spaces) do
7284
String.duplicate(" ", spaces) <> text
7385
end
7486

7587
defp remove_old_directives(ranges) do
76-
ranges =
77-
ranges
78-
|> Enum.sort_by(& &1.start.line, :desc)
79-
|> Enum.uniq_by(& &1)
80-
|> Enum.map(fn %Range{} = range ->
81-
orig_range = range
82-
83-
orig_range
84-
|> put_in([:start, :character], 1)
85-
|> update_in([:end], fn %Position{} = pos ->
86-
%Position{pos | character: 1, line: pos.line + 1}
87-
end)
88-
end)
89-
90-
first_index = length(ranges) - 1
91-
9288
ranges
93-
|> Enum.with_index()
94-
|> Enum.map(fn
95-
{range, ^first_index} ->
96-
Edit.new("\n", range)
97-
98-
{range, _} ->
99-
Edit.new("", range)
89+
|> Enum.sort_by(& &1.start.line, :desc)
90+
|> Enum.uniq_by(& &1)
91+
|> Enum.map(fn %Range{} = range ->
92+
range
93+
|> put_in([:start, :character], 1)
94+
|> update_in([:end], fn %Position{} = pos ->
95+
%Position{pos | character: 1, line: pos.line + 1}
96+
end)
10097
end)
98+
|> Enum.map(&Edit.new("", &1))
10199
|> merge_adjacent_edits()
102100
end
103101

@@ -106,12 +104,12 @@ defmodule Engine.CodeMod.Directives do
106104

107105
defp merge_adjacent_edits([edit | rest]) do
108106
rest
109-
|> Enum.reduce([edit], fn %Edit{} = current, [%Edit{} = last | rest] = edits ->
107+
|> Enum.reduce([edit], fn %Edit{} = current, [%Edit{} = last | acc_rest] = edits ->
110108
with {same_text, same_text} <- {last.text, current.text},
111109
{same, same} <- {to_tuple(current.range.end), to_tuple(last.range.start)} do
112110
collapsed = put_in(current.range.end, last.range.end)
113111

114-
[collapsed | rest]
112+
[collapsed | acc_rest]
115113
else
116114
_ ->
117115
[current | edits]

apps/engine/test/engine/code_action/handlers/organize_aliases_test.exs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,29 @@ defmodule Engine.CodeAction.Handlers.OrganizeAliasesTest do
132132
assert expected == organized
133133
end
134134

135+
test "aliases are sorted alphabetically" do
136+
{:ok, organized} =
137+
~q[
138+
defmodule SortAliases do
139+
alias A|
140+
alias C
141+
alias D
142+
alias B
143+
end
144+
]
145+
|> organize_aliases()
146+
147+
expected = ~q[
148+
defmodule SortAliases do
149+
alias A
150+
alias B
151+
alias C
152+
alias D
153+
end
154+
]t
155+
assert expected == organized
156+
end
157+
135158
test "aliases are removed duplicate aliases" do
136159
{:ok, organized} =
137160
~q[

apps/engine/test/engine/code_mod/directives_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,11 @@ defmodule Engine.CodeMod.DirectivesTest do
9292
range: &range_of/1
9393
)
9494

95-
# expect old ranges removed (replaced/emptied) and new block inserted
96-
assert length(edits) == 3
97-
assert Enum.any?(edits, &(&1.text == "\n"))
98-
assert Enum.any?(edits, &String.contains?(&1.text, "decl a"))
99-
assert Enum.any?(edits, &String.contains?(&1.text, "decl b"))
95+
assert length(edits) >= 2
96+
[insert_edit | delete_edits] = edits
97+
assert String.contains?(insert_edit.text, "decl a")
98+
assert String.contains?(insert_edit.text, "decl b")
99+
assert Enum.all?(delete_edits, &(&1.text == ""))
100100
end
101101
end
102102
end

apps/forge/lib/test/code_mod_case.ex

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ defmodule Forge.Test.CodeMod.Case do
4343
def apply_edits(original, text_edits, opts) do
4444
document = Document.new("file:///file.ex", original, 0)
4545
utf8_edits = Enum.map(text_edits, &convert_edit_utf16_to_utf8(document, &1))
46-
{:ok, edited_document} = Document.apply_content_changes(document, 1, utf8_edits)
46+
47+
sorted_edits = sort_edits_for_application(utf8_edits)
48+
49+
{:ok, edited_document} = Document.apply_content_changes(document, 1, sorted_edits)
4750
edited_document = Document.to_string(edited_document)
4851

4952
if Keyword.get(opts, :trim, true) do
@@ -53,6 +56,21 @@ defmodule Forge.Test.CodeMod.Case do
5356
end
5457
end
5558

59+
defp sort_edits_for_application(edits) do
60+
edits
61+
|> Enum.with_index()
62+
|> Enum.sort_by(fn {edit, original_index} ->
63+
case edit.range do
64+
nil ->
65+
{0, 0, 0, original_index}
66+
67+
range ->
68+
{-range.end.line, -range.end.character, -range.start.line, original_index}
69+
end
70+
end)
71+
|> Enum.map(fn {edit, _index} -> edit end)
72+
end
73+
5674
defp convert_edit_utf16_to_utf8(document, %Document.Edit{} = edit) do
5775
case edit.range do
5876
nil ->

0 commit comments

Comments
 (0)