Skip to content

Commit ebbbd26

Browse files
committed
Support extract function call in refactor
1 parent edb4308 commit ebbbd26

File tree

2 files changed

+141
-72
lines changed

2 files changed

+141
-72
lines changed

apps/language_server/lib/language_server/experimental/code_mod/extract_function.ex

+60-63
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunction do
99
Return zipper containing AST with extracted function.
1010
"""
1111
def extract_function(zipper, start_line, end_line, function_name) do
12-
{quoted, acc} = extract_lines(zipper, start_line, end_line, function_name)
13-
zipper = Z.zip(quoted)
12+
{quoted_after_extract, acc} = extract_lines(zipper, start_line, end_line, function_name)
1413

15-
declares = vars_declared(function_name, [], acc.lines) |> Enum.uniq()
16-
used = vars_used(function_name, [], acc.lines) |> Enum.uniq()
17-
args = Enum.map(used -- declares, fn var -> {var, [], nil} end)
18-
returns = declares |> Enum.filter(&(&1 in acc.vars))
19-
{zipper, extracted} = return_declared(zipper, returns, function_name, args, acc.lines)
14+
new_function_zipper = new_function(function_name, [], acc.lines) |> Z.zip()
15+
declared_vars = vars_declared(new_function_zipper) |> Enum.uniq()
16+
used_vars = vars_used(new_function_zipper) |> Enum.uniq()
17+
18+
args = used_vars -- declared_vars
19+
returns = declared_vars |> Enum.filter(&(&1 in acc.vars))
20+
21+
{zipper, extracted} =
22+
add_returned_vars(Z.zip(quoted_after_extract), returns, function_name, args, acc.lines)
2023

2124
enclosing = acc.def
2225

@@ -101,11 +104,8 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunction do
101104
next_remove_range(zipper, from, to, acc)
102105
end
103106

104-
defp vars_declared(function_name, args, lines) do
105-
function_name
106-
|> new_function(args, lines)
107-
|> Z.zip()
108-
|> vars_declared(%{vars: []})
107+
defp vars_declared(new_function_zipper) do
108+
vars_declared(new_function_zipper, %{vars: []})
109109
end
110110

111111
defp vars_declared(nil, acc) do
@@ -124,11 +124,8 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunction do
124124
|> vars_declared(acc)
125125
end
126126

127-
defp vars_used(function_name, args, lines) do
128-
function_name
129-
|> new_function(args, lines)
130-
|> Z.zip()
131-
|> vars_used(%{vars: []})
127+
defp vars_used(new_function_zipper) do
128+
vars_used(new_function_zipper, %{vars: []})
132129
end
133130

134131
defp vars_used(nil, acc) do
@@ -147,53 +144,53 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunction do
147144
|> vars_used(acc)
148145
end
149146

150-
defp return_declared(zipper, nil = _declares, function_name, args, lines) do
151-
{zipper, new_function(function_name, args, lines)}
152-
end
153-
154-
defp return_declared(zipper, [var], function_name, args, lines) when is_atom(var) do
155-
zipper =
156-
zipper
157-
|> top_find(fn
158-
{^function_name, [], []} -> true
159-
_ -> false
160-
end)
161-
|> Z.replace({:=, [], [{var, [], nil}, {function_name, [], args}]})
162-
163-
{zipper, new_function(function_name, args, Enum.concat(lines, [{var, [], nil}]))}
164-
end
165-
166-
defp return_declared(zipper, declares, function_name, args, lines) when is_list(declares) do
167-
declares = Enum.reduce(declares, {}, fn var, acc -> Tuple.append(acc, {var, [], nil}) end)
168-
169-
zipper =
170-
zipper
171-
|> top_find(fn
172-
{^function_name, [], []} -> true
173-
_ -> false
174-
end)
175-
|> Z.replace(
176-
{:=, [],
177-
[
178-
{:__block__, [],
179-
[
180-
declares
181-
]},
182-
{function_name, [], args}
183-
]}
184-
)
147+
defp add_returned_vars(zipper, _returns = [], function_name, args, lines) do
148+
args = var_ast(args)
149+
150+
{
151+
replace_function_call(zipper, function_name, {function_name, [], args}),
152+
new_function(function_name, args, lines)
153+
}
154+
end
155+
156+
defp add_returned_vars(zipper, returns, function_name, args, lines) when is_list(returns) do
157+
args = var_ast(args)
158+
returned_vars = returned(returns)
159+
160+
{
161+
replace_function_call(
162+
zipper,
163+
function_name,
164+
{:=, [], [returned_vars, {function_name, [], args}]}
165+
),
166+
new_function(function_name, args, Enum.concat(lines, [returned_vars]))
167+
}
168+
end
169+
170+
defp var_ast(vars) when is_list(vars) do
171+
Enum.map(vars, &var_ast/1)
172+
end
185173

186-
{zipper,
187-
new_function(
188-
function_name,
189-
args,
190-
Enum.concat(lines, [
191-
{:__block__, [],
192-
[
193-
declares
194-
]}
195-
])
196-
)}
174+
defp var_ast(var) when is_atom(var) do
175+
{var, [], nil}
176+
end
177+
178+
defp returned([var]) when is_atom(var) do
179+
var_ast(var)
180+
end
181+
182+
defp returned(vars) when is_list(vars) do
183+
returned = vars |> var_ast() |> List.to_tuple()
184+
{:__block__, [], [returned]}
185+
end
186+
187+
defp replace_function_call(zipper, function_name, replace_with) do
188+
zipper
189+
|> top_find(fn
190+
{^function_name, [], []} -> true
191+
_ -> false
192+
end)
193+
|> Z.replace(replace_with)
197194
end
198195

199196
defp new_function(function_name, args, lines) do

apps/language_server/test/experimental/code_mod/extract_function_test.exs

+81-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
2121
IO.inspect(three)
2222
four = 4
2323
IO.inspect(three)
24-
IO.inspect(four)
24+
25+
IO.inspect(four: four,
26+
force_format_on_new_line_with_really_long_atom: true
27+
)
28+
2529
# comment
2630
end
2731
end
@@ -45,7 +49,11 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
4549
" IO.inspect(three)",
4650
" four = 4",
4751
" IO.inspect(three)",
48-
" IO.inspect(four)",
52+
"",
53+
" IO.inspect(",
54+
" four: four,",
55+
" force_format_on_new_line_with_really_long_atom: true",
56+
" )",
4957
"",
5058
" # comment",
5159
" end",
@@ -74,7 +82,11 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
7482
" IO.inspect(three)",
7583
" four = 4",
7684
" IO.inspect(three)",
77-
" IO.inspect(four)",
85+
"",
86+
" IO.inspect(",
87+
" four: four,",
88+
" force_format_on_new_line_with_really_long_atom: true",
89+
" )",
7890
"",
7991
" # comment",
8092
" end",
@@ -101,7 +113,11 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
101113
" def foo(one, two) do",
102114
" {three, four} = bar(one, two)",
103115
" IO.inspect(three)",
104-
" IO.inspect(four)",
116+
"",
117+
" IO.inspect(",
118+
" four: four,",
119+
" force_format_on_new_line_with_really_long_atom: true",
120+
" )",
105121
"",
106122
" # comment",
107123
" end",
@@ -130,7 +146,11 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
130146
"defmodule Baz4 do",
131147
" def foo(one, two) do",
132148
" four = bar(one, two)",
133-
" IO.inspect(four)",
149+
"",
150+
" IO.inspect(",
151+
" four: four,",
152+
" force_format_on_new_line_with_really_long_atom: true",
153+
" )",
134154
"",
135155
" # comment",
136156
" end",
@@ -142,6 +162,7 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
142162
" IO.inspect(three)",
143163
" four = 4",
144164
" IO.inspect(three)",
165+
"",
145166
" four",
146167
" end",
147168
"end"
@@ -150,37 +171,88 @@ defmodule ElixirLS.LanguageServer.Experimental.CodeMod.ExtractFunctionTest do
150171

151172
Code.eval_string(source)
152173
end
174+
175+
@tag no: 5
176+
test "extracts when extract partial function call", %{quoted: quoted} do
177+
zipper = ExtractFunction.extract_function(Z.zip(quoted), 10, 10, :bar)
178+
source = Sourceror.to_string(zipper)
179+
180+
assert [
181+
"defmodule Baz5 do",
182+
" def foo(one, two) do",
183+
" three = 3",
184+
" IO.inspect(one)",
185+
" IO.inspect(two)",
186+
" IO.inspect(three)",
187+
" four = 4",
188+
" IO.inspect(three)",
189+
"",
190+
" bar(four)",
191+
"",
192+
" # comment",
193+
" end",
194+
"",
195+
" def bar(four) do",
196+
" IO.inspect(",
197+
" four: four,",
198+
" force_format_on_new_line_with_really_long_atom: true",
199+
" )",
200+
" end",
201+
"end"
202+
] ==
203+
source |> String.split("\n")
204+
205+
Code.eval_string(source)
206+
end
153207
end
154208

155209
describe "extract_lines/3" do
210+
@tag no: 20
156211
test "extract one line to function", %{quoted: quoted} do
157212
{zipper, lines} = ExtractFunction.extract_lines(Z.zip(quoted), 3, 3)
158213

159-
assert "defmodule Baz do\n def foo(one, two) do\n IO.inspect(one)\n IO.inspect(two)\n IO.inspect(three)\n four = 4\n IO.inspect(three)\n IO.inspect(four)\n\n # comment\n end\nend" ==
214+
assert "defmodule Baz20 do\n def foo(one, two) do\n IO.inspect(one)\n IO.inspect(two)\n IO.inspect(three)\n four = 4\n IO.inspect(three)\n\n IO.inspect(\n four: four,\n force_format_on_new_line_with_really_long_atom: true\n )\n\n # comment\n end\nend" ==
160215
Sourceror.to_string(zipper)
161216

162217
assert [
163218
"{:def, :foo}",
164-
"{:def_end, 11}",
219+
"{:def_end, 15}",
165220
"{:lines, [three = 3]}",
166221
_,
167222
"{:vars, [:one, :two, :three, :four]}"
168223
] = lines |> Enum.map(&Sourceror.to_string(&1))
169224
end
170225

226+
@tag no: 21
171227
test "extract multiple lines to function", %{quoted: quoted} do
172228
{zipper, lines} = ExtractFunction.extract_lines(Z.zip(quoted), 3, 4)
173229

174-
assert "defmodule Baz do\n def foo(one, two) do\n IO.inspect(two)\n IO.inspect(three)\n four = 4\n IO.inspect(three)\n IO.inspect(four)\n\n # comment\n end\nend" =
230+
assert "defmodule Baz21 do\n def foo(one, two) do\n IO.inspect(two)\n IO.inspect(three)\n four = 4\n IO.inspect(three)\n\n IO.inspect(\n four: four,\n force_format_on_new_line_with_really_long_atom: true\n )\n\n # comment\n end\nend" =
175231
Sourceror.to_string(zipper)
176232

177233
assert [
178234
"{:def, :foo}",
179-
"{:def_end, 11}",
235+
"{:def_end, 15}",
180236
"{:lines, [three = 3, IO.inspect(one)]}",
181237
_,
182238
"{:vars, [:two, :three, :four]}"
183239
] = lines |> Enum.map(&Sourceror.to_string(&1))
184240
end
241+
242+
@tag no: 22
243+
test "extract multi-line function call to function", %{quoted: quoted} do
244+
{zipper, lines} = ExtractFunction.extract_lines(Z.zip(quoted), 10, 10)
245+
246+
assert "defmodule Baz22 do\n def foo(one, two) do\n three = 3\n IO.inspect(one)\n IO.inspect(two)\n IO.inspect(three)\n four = 4\n IO.inspect(three)\n\n # comment\n end\nend" =
247+
Sourceror.to_string(zipper)
248+
249+
assert [
250+
"{:def, :foo}",
251+
"{:def_end, 15}",
252+
"{:lines,\n [\n IO.inspect(\n four: four,\n force_format_on_new_line_with_really_long_atom: true\n )\n ]}",
253+
"{:replace_with, nil}",
254+
"{:vars, []}"
255+
] = lines |> Enum.map(&Sourceror.to_string(&1))
256+
end
185257
end
186258
end

0 commit comments

Comments
 (0)