Skip to content

__MODULE__ improvements #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions lib/elixir_sense/core/metadata_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
end

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

{{:., _, [{:__aliases__, _, _}, _]} = ast_part, position, []} ->
{{:., _, [_ | _]} = ast_part, position, []} ->
{ast_part, position, fake_params}
end

Expand All @@ -589,14 +589,39 @@ defmodule ElixirSense.Core.MetadataBuilder do
end

defp pre(
{{:., _, [{:__aliases__, _, mod_path}, call]}, [line: line, column: col], params} = ast,
{{:., _, [{:__aliases__, _, module_expression = [_ | _]}, call]},
[line: line, column: col], params} = ast,
state
)
when is_call(call, params) do
mod = Module.concat(mod_path)
module = concat_module_expression(state, module_expression)

state
|> add_call_to_line({module, call, length(params)}, line, col)
|> add_current_env_to_line(line)
|> result(ast)
end

defp pre(
{{:., _, [{:__MODULE__, _, nil}, call]}, [line: line, column: col], params} = ast,
state
)
when is_call(call, params) do
module = get_current_module(state)

state
|> add_call_to_line({module, call, length(params)}, line, col)
|> add_current_env_to_line(line)
|> result(ast)
end

defp pre(
{{:., _, [module, call]}, [line: line, column: col], params} = ast,
state
)
when is_atom(module) and is_call(call, params) do
state
|> add_call_to_line({mod, call, length(params)}, line, col)
|> add_call_to_line({module, call, length(params)}, line, col)
|> add_current_env_to_line(line)
|> result(ast)
end
Expand Down
20 changes: 20 additions & 0 deletions lib/elixir_sense/providers/suggestion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,35 @@ defmodule ElixirSense.Providers.Suggestion do
nil ->
{hint, ""}

:__MODULE__ ->
# v1.2 alias syntax detected with __MODULE__ special form
# prepend module prefix before running completion
prefix = "__MODULE__."
{prefix <> hint, prefix}

module ->
# v1.2 alias syntax detected
# prepend module prefix before running completion
prefix = inspect(module) <> "."
{prefix <> hint, prefix}
end

{hint, module_special_form_replaced} =
if String.starts_with?(hint, "__MODULE__") do
{hint |> String.replace_leading("__MODULE__", inspect(module)), true}
else
{hint, false}
end

{%{type: :hint, value: prefixed_value}, suggestions} = Complete.run(hint, env)

prefixed_value =
if module_special_form_replaced do
prefixed_value |> String.replace_leading(inspect(module), "__MODULE__")
else
prefixed_value
end

# drop module prefix from hint if added
value =
if prefix != "" do
Expand Down
152 changes: 152 additions & 0 deletions test/elixir_sense/core/metadata_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2066,6 +2066,70 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
assert get_line_behaviours(state, 4) == [Exception]
end

test "registers calls with __MODULE__" do
state =
"""
defmodule NyModule do
def func1, do: :ok
def func2(a), do: :ok
def func do
__MODULE__.func1
__MODULE__.func1()
__MODULE__.func2(2)
__MODULE__.Sub.func2(2)
end
end
"""
|> string_to_state

assert state.calls == %{
5 => [%{arity: 0, col: 15, func: :func1, line: 5, mod: NyModule}],
6 => [%{arity: 0, col: 15, func: :func1, line: 6, mod: NyModule}],
7 => [%{arity: 1, col: 15, func: :func2, line: 7, mod: NyModule}],
8 => [%{arity: 1, col: 19, func: :func2, line: 8, mod: NyModule.Sub}]
}
end

test "registers calls with erlang module" do
state =
"""
defmodule NyModule do
def func do
:erl_mod.func1
:erl_mod.func1()
:erl_mod.func2(2)
end
end
"""
|> string_to_state

assert state.calls == %{
3 => [%{arity: 0, col: 13, func: :func1, line: 3, mod: :erl_mod}],
4 => [%{arity: 0, col: 13, func: :func1, line: 4, mod: :erl_mod}],
5 => [%{arity: 1, col: 13, func: :func2, line: 5, mod: :erl_mod}]
}
end

test "registers calls with atom module" do
state =
"""
defmodule NyModule do
def func do
:"Elixir.MyMod".func1
:"Elixir.MyMod".func1()
:"Elixir.MyMod".func2(2)
end
end
"""
|> string_to_state

assert state.calls == %{
3 => [%{arity: 0, col: 20, func: :func1, line: 3, mod: MyMod}],
4 => [%{arity: 0, col: 20, func: :func1, line: 4, mod: MyMod}],
5 => [%{arity: 1, col: 20, func: :func2, line: 5, mod: MyMod}]
}
end

test "registers calls no arg no parens" do
state =
"""
Expand Down Expand Up @@ -2150,6 +2214,20 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
assert state.calls == %{3 => [%{arity: 1, col: 10, func: :func, line: 3, mod: MyMod}]}
end

test "registers calls pipe with __MODULE__ operator no parens" do
state =
"""
defmodule NyModule do
def func do
"test" |> __MODULE__.func
end
end
"""
|> string_to_state

assert state.calls == %{3 => [%{arity: 1, col: 25, func: :func, line: 3, mod: NyModule}]}
end

test "registers calls pipe operator no parens" do
state =
"""
Expand Down Expand Up @@ -2192,6 +2270,34 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
assert state.calls == %{3 => [%{arity: 2, col: 20, func: :func, line: 3, mod: MyMod}]}
end

test "registers calls pipe operator erlang module" do
state =
"""
defmodule NyModule do
def func do
"test" |> :my_mod.func("arg")
end
end
"""
|> string_to_state

assert state.calls == %{3 => [%{arity: 2, col: 22, func: :func, line: 3, mod: :my_mod}]}
end

test "registers calls pipe operator atom module" do
state =
"""
defmodule NyModule do
def func do
"test" |> :"Elixir.MyMod".func("arg")
end
end
"""
|> string_to_state

assert state.calls == %{3 => [%{arity: 2, col: 30, func: :func, line: 3, mod: MyMod}]}
end

test "registers calls pipe operator local" do
state =
"""
Expand Down Expand Up @@ -2282,6 +2388,24 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
}
end

test "registers calls capture operator __MODULE__" do
state =
"""
defmodule NyModule do
def func do
&__MODULE__.func/1
&__MODULE__.Sub.func/1
end
end
"""
|> string_to_state

assert state.calls == %{
3 => [%{arity: 1, col: 16, func: :func, line: 3, mod: NyModule}],
4 => [%{arity: 1, col: 20, func: :func, line: 4, mod: NyModule.Sub}]
}
end

test "registers calls capture operator external" do
state =
"""
Expand All @@ -2296,6 +2420,34 @@ defmodule ElixirSense.Core.MetadataBuilderTest do
assert state.calls == %{3 => [%{arity: 1, col: 11, func: :func, line: 3, mod: MyMod}]}
end

test "registers calls capture operator external erlang module" do
state =
"""
defmodule NyModule do
def func do
&:erl_mod.func/1
end
end
"""
|> string_to_state

assert state.calls == %{3 => [%{arity: 1, col: 14, func: :func, line: 3, mod: :erl_mod}]}
end

test "registers calls capture operator external atom module" do
state =
"""
defmodule NyModule do
def func do
&:"Elixir.MyMod".func/1
end
end
"""
|> string_to_state

assert state.calls == %{3 => [%{arity: 1, col: 21, func: :func, line: 3, mod: MyMod}]}
end

test "registers calls capture operator local" do
state =
"""
Expand Down
71 changes: 71 additions & 0 deletions test/elixir_sense/suggestions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,52 @@ defmodule ElixirSense.SuggestionsTest do
] = ElixirSense.suggestions(buffer, 9, 7)
end

test "functions and module suggestions with __MODULE__" do
buffer = """
defmodule ElixirSenseExample.SmodO do
def test_fun_pub(a), do: :ok
defp test_fun_priv(), do: :ok
end

defmodule ElixirSenseExample do
def test_fun_priv1(a), do: :ok
def some_fun() do
__MODULE__.Sm
__MODULE__.SmodO.te
__MODULE__.te
end
end
"""

assert [
%{type: :hint, value: "__MODULE__.SmodO"},
%{
name: "SmodO",
type: :module
}
] = ElixirSense.suggestions(buffer, 9, 18)

assert [
%{type: :hint, value: "__MODULE__.SmodO.test_fun_pub"},
%{
arity: 1,
name: "test_fun_pub",
origin: "ElixirSenseExample.SmodO",
type: "function"
}
] = ElixirSense.suggestions(buffer, 10, 24)

assert [
%{type: :hint, value: "__MODULE__.test_fun_priv1"},
%{
arity: 1,
name: "test_fun_priv1",
origin: "ElixirSenseExample",
type: "function"
}
] = ElixirSense.suggestions(buffer, 11, 18)
end

test "Elixir module" do
buffer = """
defmodule MyModule do
Expand Down Expand Up @@ -1184,6 +1230,19 @@ defmodule ElixirSense.SuggestionsTest do
] = list
end

test "suggest modules to alias with __MODULE__" do
buffer = """
defmodule Stream do
alias __MODULE__.Re
end
"""

list = ElixirSense.suggestions(buffer, 2, 22)

assert [%{type: :hint, value: "__MODULE__.Reducers"}, %{name: "Reducers", type: :module} | _] =
list
end

test "suggest modules to alias v1.2 syntax" do
buffer = """
defmodule MyModule do
Expand All @@ -1196,6 +1255,18 @@ defmodule ElixirSense.SuggestionsTest do
assert [%{type: :hint, value: "Reducers"}, %{name: "Reducers", type: :module}] = list
end

test "suggest modules to alias v1.2 syntax with __MODULE__" do
buffer = """
defmodule Stream do
alias __MODULE__.{Re
end
"""

list = ElixirSense.suggestions(buffer, 2, 23)

assert [%{type: :hint, value: "Reducers"}, %{name: "Reducers", type: :module}] = list
end

describe "suggestion for param options" do
test "suggest more than one option" do
buffer = "Local.func_with_options("
Expand Down