Skip to content

Commit 352d755

Browse files
authored
Merge pull request #57 from lukaszsamson/ls-module-suggestions
__MODULE__ improvements
2 parents cabaf34 + 4de71b7 commit 352d755

File tree

4 files changed

+273
-5
lines changed

4 files changed

+273
-5
lines changed

lib/elixir_sense/core/metadata_builder.ex

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
447447
end
448448

449449
defp pre({var_or_call, [line: line, column: column], context} = ast, state)
450-
when is_atom(var_or_call) and context in [nil, Elixir] do
450+
when is_atom(var_or_call) and var_or_call != :__MODULE__ and context in [nil, Elixir] do
451451
if Enum.any?(get_current_vars(state), &(&1.name == var_or_call)) do
452452
state
453453
|> add_vars(find_vars(ast), false)
@@ -567,7 +567,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
567567
{func, position, nil} ->
568568
{func, position, fake_params}
569569

570-
{{:., _, [{:__aliases__, _, _}, _]} = ast_part, position, []} ->
570+
{{:., _, [_ | _]} = ast_part, position, []} ->
571571
{ast_part, position, fake_params}
572572
end
573573

@@ -589,14 +589,39 @@ defmodule ElixirSense.Core.MetadataBuilder do
589589
end
590590

591591
defp pre(
592-
{{:., _, [{:__aliases__, _, mod_path}, call]}, [line: line, column: col], params} = ast,
592+
{{:., _, [{:__aliases__, _, module_expression = [_ | _]}, call]},
593+
[line: line, column: col], params} = ast,
593594
state
594595
)
595596
when is_call(call, params) do
596-
mod = Module.concat(mod_path)
597+
module = concat_module_expression(state, module_expression)
598+
599+
state
600+
|> add_call_to_line({module, call, length(params)}, line, col)
601+
|> add_current_env_to_line(line)
602+
|> result(ast)
603+
end
597604

605+
defp pre(
606+
{{:., _, [{:__MODULE__, _, nil}, call]}, [line: line, column: col], params} = ast,
607+
state
608+
)
609+
when is_call(call, params) do
610+
module = get_current_module(state)
611+
612+
state
613+
|> add_call_to_line({module, call, length(params)}, line, col)
614+
|> add_current_env_to_line(line)
615+
|> result(ast)
616+
end
617+
618+
defp pre(
619+
{{:., _, [module, call]}, [line: line, column: col], params} = ast,
620+
state
621+
)
622+
when is_atom(module) and is_call(call, params) do
598623
state
599-
|> add_call_to_line({mod, call, length(params)}, line, col)
624+
|> add_call_to_line({module, call, length(params)}, line, col)
600625
|> add_current_env_to_line(line)
601626
|> result(ast)
602627
end

lib/elixir_sense/providers/suggestion.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,35 @@ defmodule ElixirSense.Providers.Suggestion do
297297
nil ->
298298
{hint, ""}
299299

300+
:__MODULE__ ->
301+
# v1.2 alias syntax detected with __MODULE__ special form
302+
# prepend module prefix before running completion
303+
prefix = "__MODULE__."
304+
{prefix <> hint, prefix}
305+
300306
module ->
301307
# v1.2 alias syntax detected
302308
# prepend module prefix before running completion
303309
prefix = inspect(module) <> "."
304310
{prefix <> hint, prefix}
305311
end
306312

313+
{hint, module_special_form_replaced} =
314+
if String.starts_with?(hint, "__MODULE__") do
315+
{hint |> String.replace_leading("__MODULE__", inspect(module)), true}
316+
else
317+
{hint, false}
318+
end
319+
307320
{%{type: :hint, value: prefixed_value}, suggestions} = Complete.run(hint, env)
308321

322+
prefixed_value =
323+
if module_special_form_replaced do
324+
prefixed_value |> String.replace_leading(inspect(module), "__MODULE__")
325+
else
326+
prefixed_value
327+
end
328+
309329
# drop module prefix from hint if added
310330
value =
311331
if prefix != "" do

test/elixir_sense/core/metadata_builder_test.exs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,70 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
20662066
assert get_line_behaviours(state, 4) == [Exception]
20672067
end
20682068

2069+
test "registers calls with __MODULE__" do
2070+
state =
2071+
"""
2072+
defmodule NyModule do
2073+
def func1, do: :ok
2074+
def func2(a), do: :ok
2075+
def func do
2076+
__MODULE__.func1
2077+
__MODULE__.func1()
2078+
__MODULE__.func2(2)
2079+
__MODULE__.Sub.func2(2)
2080+
end
2081+
end
2082+
"""
2083+
|> string_to_state
2084+
2085+
assert state.calls == %{
2086+
5 => [%{arity: 0, col: 15, func: :func1, line: 5, mod: NyModule}],
2087+
6 => [%{arity: 0, col: 15, func: :func1, line: 6, mod: NyModule}],
2088+
7 => [%{arity: 1, col: 15, func: :func2, line: 7, mod: NyModule}],
2089+
8 => [%{arity: 1, col: 19, func: :func2, line: 8, mod: NyModule.Sub}]
2090+
}
2091+
end
2092+
2093+
test "registers calls with erlang module" do
2094+
state =
2095+
"""
2096+
defmodule NyModule do
2097+
def func do
2098+
:erl_mod.func1
2099+
:erl_mod.func1()
2100+
:erl_mod.func2(2)
2101+
end
2102+
end
2103+
"""
2104+
|> string_to_state
2105+
2106+
assert state.calls == %{
2107+
3 => [%{arity: 0, col: 13, func: :func1, line: 3, mod: :erl_mod}],
2108+
4 => [%{arity: 0, col: 13, func: :func1, line: 4, mod: :erl_mod}],
2109+
5 => [%{arity: 1, col: 13, func: :func2, line: 5, mod: :erl_mod}]
2110+
}
2111+
end
2112+
2113+
test "registers calls with atom module" do
2114+
state =
2115+
"""
2116+
defmodule NyModule do
2117+
def func do
2118+
:"Elixir.MyMod".func1
2119+
:"Elixir.MyMod".func1()
2120+
:"Elixir.MyMod".func2(2)
2121+
end
2122+
end
2123+
"""
2124+
|> string_to_state
2125+
2126+
assert state.calls == %{
2127+
3 => [%{arity: 0, col: 20, func: :func1, line: 3, mod: MyMod}],
2128+
4 => [%{arity: 0, col: 20, func: :func1, line: 4, mod: MyMod}],
2129+
5 => [%{arity: 1, col: 20, func: :func2, line: 5, mod: MyMod}]
2130+
}
2131+
end
2132+
20692133
test "registers calls no arg no parens" do
20702134
state =
20712135
"""
@@ -2150,6 +2214,20 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
21502214
assert state.calls == %{3 => [%{arity: 1, col: 10, func: :func, line: 3, mod: MyMod}]}
21512215
end
21522216

2217+
test "registers calls pipe with __MODULE__ operator no parens" do
2218+
state =
2219+
"""
2220+
defmodule NyModule do
2221+
def func do
2222+
"test" |> __MODULE__.func
2223+
end
2224+
end
2225+
"""
2226+
|> string_to_state
2227+
2228+
assert state.calls == %{3 => [%{arity: 1, col: 25, func: :func, line: 3, mod: NyModule}]}
2229+
end
2230+
21532231
test "registers calls pipe operator no parens" do
21542232
state =
21552233
"""
@@ -2192,6 +2270,34 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
21922270
assert state.calls == %{3 => [%{arity: 2, col: 20, func: :func, line: 3, mod: MyMod}]}
21932271
end
21942272

2273+
test "registers calls pipe operator erlang module" do
2274+
state =
2275+
"""
2276+
defmodule NyModule do
2277+
def func do
2278+
"test" |> :my_mod.func("arg")
2279+
end
2280+
end
2281+
"""
2282+
|> string_to_state
2283+
2284+
assert state.calls == %{3 => [%{arity: 2, col: 22, func: :func, line: 3, mod: :my_mod}]}
2285+
end
2286+
2287+
test "registers calls pipe operator atom module" do
2288+
state =
2289+
"""
2290+
defmodule NyModule do
2291+
def func do
2292+
"test" |> :"Elixir.MyMod".func("arg")
2293+
end
2294+
end
2295+
"""
2296+
|> string_to_state
2297+
2298+
assert state.calls == %{3 => [%{arity: 2, col: 30, func: :func, line: 3, mod: MyMod}]}
2299+
end
2300+
21952301
test "registers calls pipe operator local" do
21962302
state =
21972303
"""
@@ -2282,6 +2388,24 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
22822388
}
22832389
end
22842390

2391+
test "registers calls capture operator __MODULE__" do
2392+
state =
2393+
"""
2394+
defmodule NyModule do
2395+
def func do
2396+
&__MODULE__.func/1
2397+
&__MODULE__.Sub.func/1
2398+
end
2399+
end
2400+
"""
2401+
|> string_to_state
2402+
2403+
assert state.calls == %{
2404+
3 => [%{arity: 1, col: 16, func: :func, line: 3, mod: NyModule}],
2405+
4 => [%{arity: 1, col: 20, func: :func, line: 4, mod: NyModule.Sub}]
2406+
}
2407+
end
2408+
22852409
test "registers calls capture operator external" do
22862410
state =
22872411
"""
@@ -2296,6 +2420,34 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
22962420
assert state.calls == %{3 => [%{arity: 1, col: 11, func: :func, line: 3, mod: MyMod}]}
22972421
end
22982422

2423+
test "registers calls capture operator external erlang module" do
2424+
state =
2425+
"""
2426+
defmodule NyModule do
2427+
def func do
2428+
&:erl_mod.func/1
2429+
end
2430+
end
2431+
"""
2432+
|> string_to_state
2433+
2434+
assert state.calls == %{3 => [%{arity: 1, col: 14, func: :func, line: 3, mod: :erl_mod}]}
2435+
end
2436+
2437+
test "registers calls capture operator external atom module" do
2438+
state =
2439+
"""
2440+
defmodule NyModule do
2441+
def func do
2442+
&:"Elixir.MyMod".func/1
2443+
end
2444+
end
2445+
"""
2446+
|> string_to_state
2447+
2448+
assert state.calls == %{3 => [%{arity: 1, col: 21, func: :func, line: 3, mod: MyMod}]}
2449+
end
2450+
22992451
test "registers calls capture operator local" do
23002452
state =
23012453
"""

test/elixir_sense/suggestions_test.exs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,52 @@ defmodule ElixirSense.SuggestionsTest do
901901
] = ElixirSense.suggestions(buffer, 9, 7)
902902
end
903903

904+
test "functions and module suggestions with __MODULE__" do
905+
buffer = """
906+
defmodule ElixirSenseExample.SmodO do
907+
def test_fun_pub(a), do: :ok
908+
defp test_fun_priv(), do: :ok
909+
end
910+
911+
defmodule ElixirSenseExample do
912+
def test_fun_priv1(a), do: :ok
913+
def some_fun() do
914+
__MODULE__.Sm
915+
__MODULE__.SmodO.te
916+
__MODULE__.te
917+
end
918+
end
919+
"""
920+
921+
assert [
922+
%{type: :hint, value: "__MODULE__.SmodO"},
923+
%{
924+
name: "SmodO",
925+
type: :module
926+
}
927+
] = ElixirSense.suggestions(buffer, 9, 18)
928+
929+
assert [
930+
%{type: :hint, value: "__MODULE__.SmodO.test_fun_pub"},
931+
%{
932+
arity: 1,
933+
name: "test_fun_pub",
934+
origin: "ElixirSenseExample.SmodO",
935+
type: "function"
936+
}
937+
] = ElixirSense.suggestions(buffer, 10, 24)
938+
939+
assert [
940+
%{type: :hint, value: "__MODULE__.test_fun_priv1"},
941+
%{
942+
arity: 1,
943+
name: "test_fun_priv1",
944+
origin: "ElixirSenseExample",
945+
type: "function"
946+
}
947+
] = ElixirSense.suggestions(buffer, 11, 18)
948+
end
949+
904950
test "Elixir module" do
905951
buffer = """
906952
defmodule MyModule do
@@ -1184,6 +1230,19 @@ defmodule ElixirSense.SuggestionsTest do
11841230
] = list
11851231
end
11861232

1233+
test "suggest modules to alias with __MODULE__" do
1234+
buffer = """
1235+
defmodule Stream do
1236+
alias __MODULE__.Re
1237+
end
1238+
"""
1239+
1240+
list = ElixirSense.suggestions(buffer, 2, 22)
1241+
1242+
assert [%{type: :hint, value: "__MODULE__.Reducers"}, %{name: "Reducers", type: :module} | _] =
1243+
list
1244+
end
1245+
11871246
test "suggest modules to alias v1.2 syntax" do
11881247
buffer = """
11891248
defmodule MyModule do
@@ -1196,6 +1255,18 @@ defmodule ElixirSense.SuggestionsTest do
11961255
assert [%{type: :hint, value: "Reducers"}, %{name: "Reducers", type: :module}] = list
11971256
end
11981257

1258+
test "suggest modules to alias v1.2 syntax with __MODULE__" do
1259+
buffer = """
1260+
defmodule Stream do
1261+
alias __MODULE__.{Re
1262+
end
1263+
"""
1264+
1265+
list = ElixirSense.suggestions(buffer, 2, 23)
1266+
1267+
assert [%{type: :hint, value: "Reducers"}, %{name: "Reducers", type: :module}] = list
1268+
end
1269+
11991270
describe "suggestion for param options" do
12001271
test "suggest more than one option" do
12011272
buffer = "Local.func_with_options("

0 commit comments

Comments
 (0)