From d62809ec470855703311d3b8cd72f7d6cb9eabec Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Wed, 17 Apr 2024 08:38:31 -0400 Subject: [PATCH] fix(completions): completions inside alias/import/require special forms (#422) Closes #421 --- priv/monkey/_next_ls_private_compiler.ex | 52 +++++++++++++++-------- test/next_ls/completions_test.exs | 53 +++++++++++++++++++++++- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/priv/monkey/_next_ls_private_compiler.ex b/priv/monkey/_next_ls_private_compiler.ex index bfe96131..06177a2e 100644 --- a/priv/monkey/_next_ls_private_compiler.ex +++ b/priv/monkey/_next_ls_private_compiler.ex @@ -1213,38 +1213,56 @@ if Version.match?(System.version(), ">= 1.17.0-dev") do expand({form, meta, [arg, []]}, state, env) end - defp expand({:alias, meta, [arg, opts]}, state, env) do + defp expand({:alias, meta, [arg, opts]} = node, state, env) do {arg, state, env} = expand(arg, state, env) {opts, state, env} = expand_directive_opts(opts, state, env) - # An actual compiler would raise if the alias fails. - case Macro.Env.define_alias(env, meta, arg, [trace: false] ++ opts) do - {:ok, env} -> {arg, state, env} - {:error, _} -> {arg, state, env} + case arg do + {:__aliases__, _, _} -> + # An actual compiler would raise if the alias fails. + case Macro.Env.define_alias(env, meta, arg, [trace: false] ++ opts) do + {:ok, env} -> {arg, state, env} + {:error, _} -> {arg, state, env} + end + + _ -> + {node, state, env} end end - defp expand({:require, meta, [arg, opts]}, state, env) do + defp expand({:require, meta, [arg, opts]} = node, state, env) do {arg, state, env} = expand(arg, state, env) {opts, state, env} = expand_directive_opts(opts, state, env) - # An actual compiler would raise if the module is not defined or if the require fails. - case Macro.Env.define_require(env, meta, arg, [trace: false] ++ opts) do - {:ok, env} -> {arg, state, env} - {:error, _} -> {arg, state, env} + case arg do + {:__aliases__, _, _} -> + # An actual compiler would raise if the module is not defined or if the require fails. + case Macro.Env.define_require(env, meta, arg, [trace: false] ++ opts) do + {:ok, env} -> {arg, state, env} + {:error, _} -> {arg, state, env} + end + + _ -> + {node, state, env} end end - defp expand({:import, meta, [arg, opts]}, state, env) do + defp expand({:import, meta, [arg, opts]} = node, state, env) do {arg, state, env} = expand(arg, state, env) {opts, state, env} = expand_directive_opts(opts, state, env) - # An actual compiler would raise if the module is not defined or if the import fails. - with true <- is_atom(arg) and Code.ensure_loaded?(arg), - {:ok, env} <- Macro.Env.define_import(env, meta, arg, [trace: false] ++ opts) do - {arg, state, env} - else - _ -> {arg, state, env} + case arg do + {:__aliases__, _, _} -> + # An actual compiler would raise if the module is not defined or if the import fails. + with true <- is_atom(arg) and Code.ensure_loaded?(arg), + {:ok, env} <- Macro.Env.define_import(env, meta, arg, [trace: false] ++ opts) do + {arg, state, env} + else + _ -> {arg, state, env} + end + + _ -> + {node, state, env} end end diff --git a/test/next_ls/completions_test.exs b/test/next_ls/completions_test.exs index 62472a67..7a50b743 100644 --- a/test/next_ls/completions_test.exs +++ b/test/next_ls/completions_test.exs @@ -31,6 +31,16 @@ defmodule NextLS.CompletionsTest do end """) + baz = Path.join(cwd, "my_proj/lib/baz.ex") + + File.write!(baz, """ + defmodule Foo.Bar.Baz do + def run() do + :ok + end + end + """) + [foo: foo, cwd: cwd] end @@ -285,7 +295,7 @@ defmodule NextLS.CompletionsTest do } } - assert_result 2, [_, _] = results + assert_result 2, [_, _, _] = results results end) @@ -300,6 +310,14 @@ defmodule NextLS.CompletionsTest do "label" => "bar.ex" } in results + assert %{ + "data" => nil, + "documentation" => "", + "insertText" => "baz.ex", + "kind" => 17, + "label" => "baz.ex" + } in results + assert %{ "data" => nil, "documentation" => "", @@ -342,4 +360,37 @@ defmodule NextLS.CompletionsTest do } ] end + + test "inside alias special form", %{client: client, foo: foo} do + uri = uri(foo) + + did_open(client, foo, """ + defmodule Foo do + alias Foo.Bar. + + def run() do + :ok + end + end + """) + + request client, %{ + method: "textDocument/completion", + id: 2, + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: uri + }, + position: %{ + line: 1, + character: 16 + } + } + } + + assert_result 2, [ + %{"data" => _, "documentation" => _, "insertText" => "Baz", "kind" => 9, "label" => "Baz"} + ] + end end