diff --git a/apps/debug_adapter/lib/debug_adapter/server.ex b/apps/debug_adapter/lib/debug_adapter/server.ex index c19ce5532..e2103e315 100644 --- a/apps/debug_adapter/lib/debug_adapter/server.ex +++ b/apps/debug_adapter/lib/debug_adapter/server.ex @@ -642,6 +642,12 @@ defmodule ElixirLS.DebugAdapter.Server do {:noreply, state} end + def handle_info({:ok, [%Frame{} | _]}, state = %__MODULE__{}) do + # timed out response from Stacktrace.get/1 + # we already emitted a warning + {:noreply, state} + end + # If we get the disconnect request from the client, we continue with :disconnect so the server will # die right after responding to the request @impl GenServer diff --git a/apps/debug_adapter/test/debugger_test.exs b/apps/debug_adapter/test/debugger_test.exs index 563a0a89a..57bbef8e4 100644 --- a/apps/debug_adapter/test/debugger_test.exs +++ b/apps/debug_adapter/test/debugger_test.exs @@ -1228,6 +1228,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute :hello in :int.interpreted() abs_path = Path.absname("src/hello.erl") @@ -1241,6 +1243,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do 5000 ) + Process.sleep(100) + assert :hello in :int.interpreted() assert [{{:hello, 5}, [:active, :enable, :null, _]}] = :int.all_breaks(:hello) assert %{^abs_path => [{[:hello], 5}]} = :sys.get_state(server).breakpoints @@ -1310,6 +1314,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute :hello in :int.interpreted() abs_path = Path.absname("src/hello.erl1") @@ -1358,6 +1364,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute MixProject in :int.interpreted() refute MixProject.Some in :int.interpreted() abs_path = Path.absname("lib/mix_project.ex") @@ -1462,6 +1470,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute MixProject in :int.interpreted() refute :hello in :int.interpreted() @@ -1498,6 +1508,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do 5000 ) + Process.sleep(100) + assert :hello in :int.interpreted() assert [{{:hello, 5}, _}] = :int.all_breaks(:hello) @@ -1554,6 +1566,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute MixProject in :int.interpreted() # set @@ -1649,6 +1663,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute MixProject in :int.interpreted() # set @@ -1744,6 +1760,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute MixProject in :int.interpreted() # set @@ -2066,6 +2084,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute :hello in :int.interpreted() Server.receive_packet( @@ -2140,6 +2160,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute :hello in :int.interpreted() Server.receive_packet( @@ -2184,6 +2206,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute :hello in :int.interpreted() # set @@ -2277,6 +2301,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute :hello in :int.interpreted() # set @@ -2708,6 +2734,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + assert MixProject.Dbg in :int.interpreted() Server.receive_packet(server, request(5, "configurationDone", %{})) @@ -2894,6 +2922,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + assert MixProject.Dbg in :int.interpreted() Server.receive_packet(server, request(5, "configurationDone", %{})) @@ -3062,6 +3092,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do assert_receive(response(_, 2, "launch", _), 3000) assert_receive(event(_, "initialized", %{}), 5000) + Process.sleep(100) + refute MixProject.Dbg in :int.interpreted() Server.receive_packet(server, request(5, "configurationDone", %{})) diff --git a/apps/elixir_ls_utils/test/complete_test.exs b/apps/elixir_ls_utils/test/complete_test.exs index 1ad1a31c1..df91a7e5f 100644 --- a/apps/elixir_ls_utils/test/complete_test.exs +++ b/apps/elixir_ls_utils/test/complete_test.exs @@ -2208,7 +2208,6 @@ defmodule ElixirLS.Utils.CompletionEngineTest do ] = expand(~c"unquote", %Env{requires: []}) end - test "macros from the same module should not add needed_require" do macro_info = %ElixirSense.Core.State.ModFunInfo{ type: :defmacro, diff --git a/apps/language_server/lib/language_server/providers/completion.ex b/apps/language_server/lib/language_server/providers/completion.ex index 0f9a97b60..58ad3a426 100644 --- a/apps/language_server/lib/language_server/providers/completion.ex +++ b/apps/language_server/lib/language_server/providers/completion.ex @@ -744,22 +744,53 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do } end - defp from_completion_item(%{type: :param_option} = suggestion, _context, _options) do - %{name: name, origin: _origin, doc: doc, type_spec: type_spec, expanded_spec: expanded_spec} = + defp from_completion_item(%{type: :param_option} = suggestion, context, _options) do + %{ + name: name, + type_spec: type_spec, + origin: origin, + subtype: subtype + } = suggestion formatted_spec = - if expanded_spec != "" do - "\n\n```elixir\n#{expanded_spec}\n```\n" + if type_spec != "" do + "\n\n```elixir\n#{type_spec}\n```\n" else "" end + {insert_text, text_edit} = + cond do + subtype == :keyword and not String.ends_with?(context.prefix, ":") -> + {"#{name}: ", nil} + + subtype == :keyword -> + {"", + %{ + "range" => %{ + "start" => %{ + "line" => context.line, + "character" => context.character - String.length(context.prefix) + }, + "end" => %{"line" => context.line, "character" => context.character} + }, + "newText" => "#{name}: " + }} + + match?(":" <> _, context.prefix) -> + {name, nil} + + true -> + {":#{name}", nil} + end + %__MODULE__{ label: to_string(name), - detail: "#{type_spec}", - documentation: "#{doc}#{formatted_spec}", - insert_text: "#{name}: ", + detail: "#{origin} option", + documentation: formatted_spec, + insert_text: insert_text, + text_edit: text_edit, priority: 10, kind: :field, tags: [] diff --git a/apps/language_server/lib/language_server/providers/completion/reducers/params.ex b/apps/language_server/lib/language_server/providers/completion/reducers/params.ex index 7d1a0dcc2..55a12dac6 100644 --- a/apps/language_server/lib/language_server/providers/completion/reducers/params.ex +++ b/apps/language_server/lib/language_server/providers/completion/reducers/params.ex @@ -11,16 +11,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do alias ElixirSense.Core.Introspection alias ElixirSense.Core.Metadata alias ElixirSense.Core.Source - alias ElixirSense.Core.TypeInfo alias ElixirLS.Utils.Matcher @type param_option :: %{ type: :param_option, + subtype: :keyword | :atom, name: String.t(), origin: String.t(), - type_spec: String.t(), - doc: String.t(), - expanded_spec: String.t() + type_spec: String.t() } @doc """ @@ -36,6 +34,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do with %{ candidate: {mod, fun}, elixir_prefix: elixir_prefix, + options_so_far: options_so_far, + cursor_at_option: cursor_at_option, npar: npar } <- Source.which_func(prefix, binding_env), @@ -45,19 +45,42 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do env, mods_funs, metadata_types, - {1, 1}, + cursor_context.cursor_position, not elixir_prefix ) do list = - if Code.ensure_loaded?(mod) do - TypeInfo.extract_param_options(mod, fun, npar) - |> Kernel.++(TypeInfo.extract_param_options(mod, :"MACRO-#{fun}", npar + 1)) - |> options_to_suggestions(mod) - |> Enum.filter(&Matcher.match?(&1.name, hint)) - else - # TODO metadata? - [] + for opt <- + ElixirSense.Core.Options.get_param_options(mod, fun, npar + 1, env, buffer_metadata) do + case opt do + {name, type} -> + # match on atom: + if Matcher.match?(to_string(name) <> ":", hint) do + expanded_spec = Introspection.to_string_with_parens(type) + + %{ + name: name |> Atom.to_string(), + origin: "#{inspect(mod)}.#{fun}", + type: :param_option, + subtype: :keyword, + type_spec: expanded_spec + } + end + + name -> + # match on :atom + if options_so_far == [] and cursor_at_option == true and + Matcher.match?(inspect(name), hint) do + %{ + name: name |> Atom.to_string(), + origin: "#{inspect(mod)}.#{fun}", + type: :param_option, + subtype: :atom, + type_spec: "" + } + end + end end + |> Enum.reject(&is_nil/1) {:cont, %{acc | result: acc.result ++ list}} else @@ -65,22 +88,4 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do {:cont, acc} end end - - defp options_to_suggestions(options, original_module) do - Enum.map(options, fn - {mod, name, type} -> - TypeInfo.get_type_info(mod, type, original_module) - |> Map.merge(%{type: :param_option, name: name |> Atom.to_string()}) - - {mod, name} -> - %{ - doc: "", - expanded_spec: "", - name: name |> Atom.to_string(), - origin: inspect(mod), - type: :param_option, - type_spec: "" - } - end) - end end diff --git a/apps/language_server/test/providers/completion/suggestions_test.exs b/apps/language_server/test/providers/completion/suggestions_test.exs index 9364f3b65..3b570b58b 100644 --- a/apps/language_server/test/providers/completion/suggestions_test.exs +++ b/apps/language_server/test/providers/completion/suggestions_test.exs @@ -3734,37 +3734,32 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do test "options as inline list" do buffer = "Local.func_with_options_as_inline_list(" - assert %{type_spec: "local_t()", expanded_spec: "@type local_t() :: atom()"} = + assert %{type_spec: "atom()"} = suggestion_by_name("local_o", buffer) assert %{ - type_spec: "keyword()", - expanded_spec: """ - @type keyword() :: [ - {atom(), any()} - ]\ - """ + type_spec: "keyword()" } = suggestion_by_name("builtin_o", buffer) end test "options vars defined in when" do - type_spec = "local_t()" - origin = "ElixirSenseExample.ModuleWithTypespecs.Local" - spec = "@type local_t() :: atom()" + type_spec = "atom()" + origin = "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_option_var_defined_in_when" buffer = "Local.func_with_option_var_defined_in_when(" suggestion = suggestion_by_name("local_o", buffer) assert suggestion.type_spec == type_spec assert suggestion.origin == origin - assert suggestion.expanded_spec == spec + + origin = + "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options_var_defined_in_when" buffer = "Local.func_with_options_var_defined_in_when(" suggestion = suggestion_by_name("local_o", buffer) assert suggestion.type_spec == type_spec assert suggestion.origin == origin - assert suggestion.expanded_spec == spec end test "opaque type internal structure is not revealed" do @@ -3772,38 +3767,31 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("opaque_o", buffer) assert suggestion.type_spec == "opaque_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - assert suggestion.expanded_spec == "@opaque opaque_t()" - assert suggestion.doc == "Local opaque type" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "private type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("private_o", buffer) - assert suggestion.type_spec == "private_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - assert suggestion.expanded_spec == "@typep private_t() :: atom()" - assert suggestion.doc == "" + assert suggestion.type_spec == "atom()" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "local type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("local_o", buffer) - assert suggestion.type_spec == "local_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - assert suggestion.expanded_spec == "@type local_t() :: atom()" - assert suggestion.doc == "Local type" + assert suggestion.type_spec == "atom()" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "local type with params" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("local_with_params_o", buffer) - assert suggestion.type_spec == "local_t(atom(), integer())" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - assert suggestion.expanded_spec =~ "@type local_t(a, b) ::" + assert suggestion.type_spec == "{atom(), integer()}" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "basic type" do @@ -3811,9 +3799,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("basic_o", buffer) assert suggestion.type_spec == "pid()" - assert suggestion.origin == "" - assert suggestion.expanded_spec == "" - assert suggestion.doc == "A process identifier, pid, identifies a process" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "basic type with params" do @@ -3821,9 +3807,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("basic_with_params_o", buffer) assert suggestion.type_spec == "[atom(), ...]" - assert suggestion.origin == "" - assert suggestion.expanded_spec == "" - assert suggestion.doc == "Non-empty proper list" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "built-in type" do @@ -3831,15 +3815,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("builtin_o", buffer) assert suggestion.type_spec == "keyword()" - assert suggestion.origin == "" - - assert suggestion.expanded_spec == """ - @type keyword() :: [ - {atom(), any()} - ]\ - """ - - assert suggestion.doc == "A keyword list" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "built-in type with params" do @@ -3847,41 +3823,31 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("builtin_with_params_o", buffer) assert suggestion.type_spec == "keyword(term())" - assert suggestion.origin == "" - assert suggestion.expanded_spec =~ "@type keyword(t()) ::" - assert suggestion.doc == "A keyword list with values of type `t`" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "union type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("union_o", buffer) - assert suggestion.type_spec == "union_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - - assert suggestion.expanded_spec == """ - @type union_t() :: - atom() | integer()\ - """ + assert suggestion.type_spec == "atom() | integer()" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "list type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("list_o", buffer) - assert suggestion.type_spec == "list_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - assert suggestion.expanded_spec =~ "@type list_t() ::" + assert suggestion.type_spec == "[:trace | :log]" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "remote type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("remote_o", buffer) - assert suggestion.type_spec == "ElixirSenseExample.ModuleWithTypespecs.Remote.remote_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Remote" - assert suggestion.expanded_spec == "@type remote_t() :: atom()" - assert suggestion.doc == "Remote type" + assert suggestion.type_spec == "atom()" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "remote type with args" do @@ -3889,11 +3855,9 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("remote_with_params_o", buffer) assert suggestion.type_spec == - "ElixirSenseExample.ModuleWithTypespecs.Remote.remote_t(atom(), integer())" + "{atom(), integer()}" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Remote" - assert suggestion.expanded_spec =~ "@type remote_t(a, b) ::" - assert suggestion.doc == "Remote type with params" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "remote erlang type with doc" do @@ -3901,42 +3865,26 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("erlang_t", buffer) assert suggestion.type_spec == - ":erlang.time_unit()" + "pos_integer()\n| :second\n| :millisecond\n| :microsecond\n| :nanosecond\n| :native\n| :perf_counter\n| :seconds\n| :milli_seconds\n| :micro_seconds\n| :nano_seconds" - assert suggestion.origin == ":erlang" - - assert suggestion.expanded_spec == - "@type time_unit() ::\n pos_integer()\n | :second\n | :millisecond\n | :microsecond\n | :nanosecond\n | :native\n | :perf_counter\n | deprecated_time_unit()" - - if System.otp_release() |> String.to_integer() >= 23 do - assert suggestion.doc =~ "Supported time unit representations" - end + assert suggestion.origin == + "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_erlang_type_options" end test "remote aliased type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("remote_aliased_o", buffer) - assert suggestion.type_spec == "remote_aliased_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local" - - assert suggestion.expanded_spec == """ - @type remote_aliased_t() :: - ElixirSenseExample.ModuleWithTypespecs.Remote.remote_t() - | ElixirSenseExample.ModuleWithTypespecs.Remote.remote_list_t()\ - """ - - assert suggestion.doc == "Remote type from aliased module" + assert suggestion.type_spec == "atom() | [atom()]" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "remote aliased inline type" do buffer = "Local.func_with_options(" suggestion = suggestion_by_name("remote_aliased_inline_o", buffer) - assert suggestion.type_spec == "ElixirSenseExample.ModuleWithTypespecs.Remote.remote_t()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Remote" - assert suggestion.expanded_spec == "@type remote_t() :: atom()" - assert suggestion.doc == "Remote type" + assert suggestion.type_spec == "atom()" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "inline list type" do @@ -3944,9 +3892,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do suggestion = suggestion_by_name("inline_list_o", buffer) assert suggestion.type_spec == "[:trace | :log]" - assert suggestion.origin == "" - assert suggestion.expanded_spec == "" - assert suggestion.doc == "" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "non existent type" do @@ -3956,14 +3902,12 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do assert suggestion.type_spec == "ElixirSenseExample.ModuleWithTypespecs.Remote.non_existent()" - assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Remote" - assert suggestion.expanded_spec == "" - assert suggestion.doc == "" + assert suggestion.origin == "ElixirSenseExample.ModuleWithTypespecs.Local.func_with_options" end test "named options" do buffer = "Local.func_with_named_options(" - assert suggestion_by_name("local_o", buffer).type_spec == "local_t()" + assert suggestion_by_name("local_o", buffer).type_spec == "atom()" end test "options with only one option" do @@ -3974,14 +3918,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do test "union of options" do buffer = "Local.func_with_union_of_options(" - assert suggestion_by_name("local_o", buffer).type_spec == "local_t()" + assert suggestion_by_name("local_o", buffer).type_spec == "atom()" assert suggestion_by_name("option_1", buffer).type_spec == "atom()" end test "union of options inline" do buffer = "Local.func_with_union_of_options_inline(" - assert suggestion_by_name("local_o", buffer).type_spec == "local_t()" + assert suggestion_by_name("local_o", buffer).type_spec == "atom()" assert suggestion_by_name("option_1", buffer).type_spec == "atom()" end @@ -3990,30 +3934,258 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.SuggestionTest do assert suggestion_by_name("option_1", buffer).type_spec == "boolean()" suggestion = suggestion_by_name("remote_option_1", buffer) - assert suggestion.type_spec == "ElixirSenseExample.ModuleWithTypespecs.Remote.remote_t()" - assert suggestion.expanded_spec == "@type remote_t() :: atom()" - assert suggestion.doc == "Remote type" + assert suggestion.type_spec == "atom()" + end + + if System.otp_release() |> String.to_integer() >= 25 do + test "atom only options" do + # only keyword in shorthand keyword list + buffer = ":ets.new(:name, " + assert list = suggestions_by_type(:param_option, buffer) + refute Enum.any?(list, &match?(%{name: "bag"}, &1)) + assert Enum.any?(list, &match?(%{name: "write_concurrency"}, &1)) + + buffer = ":ets.new(:name, heir: pid, " + assert list = suggestions_by_type(:param_option, buffer) + refute Enum.any?(list, &match?(%{name: "bag"}, &1)) + assert Enum.any?(list, &match?(%{name: "write_concurrency"}, &1)) + + # suggest atom options in list + buffer = ":ets.new(:name, [" + assert list = suggestions_by_type(:param_option, buffer) + assert Enum.any?(list, &match?(%{name: "bag"}, &1)) + assert Enum.any?(list, &match?(%{name: "set"}, &1)) + assert Enum.any?(list, &match?(%{name: "write_concurrency"}, &1)) + + buffer = ":ets.new(:name, [:set, " + assert list = suggestions_by_type(:param_option, buffer) + assert Enum.any?(list, &match?(%{name: "bag"}, &1)) + # refute Enum.any?(list, &match?(%{name: "set"}, &1)) + assert Enum.any?(list, &match?(%{name: "write_concurrency"}, &1)) + + # no atoms after keyword pair + buffer = ":ets.new(:name, [:set, heir: pid, " + assert list = suggestions_by_type(:param_option, buffer) + refute Enum.any?(list, &match?(%{name: "bag"}, &1)) + assert Enum.any?(list, &match?(%{name: "write_concurrency"}, &1)) + end end - test "atom only options" do - buffer = ":ets.new(:name," + test "format type spec" do + buffer = "Local.func_with_options(" - assert suggestion_by_name("duplicate_bag", buffer).type_spec == "" - assert suggestion_by_name("named_table", buffer).doc == "" + assert suggestion_by_name("large_o", buffer).type_spec == + "pid() | port() | (registered_name :: atom()) | {registered_name :: atom(), node()}" end - test "format type spec" do - buffer = "Local.func_with_options(" + test "params with default args" do + buffer = """ + ElixirSenseExample.ModuleWithTypespecs.Local.fun_with_default() + """ + + list = Suggestion.suggestions(buffer, 1, 63) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "params with multiple specs" do + buffer = """ + ElixirSenseExample.ModuleWithTypespecs.Local.fun_with_multiple_specs() + """ + + list = Suggestion.suggestions(buffer, 1, 70) + assert [%{name: "opt_name"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "params with multiple functions" do + buffer = """ + ElixirSenseExample.ModuleWithTypespecs.Local.multiple_functions() + """ + + list = Suggestion.suggestions(buffer, 1, 65) + assert [%{name: "foo"}, %{name: "bar"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "params from callback" do + buffer = """ + ElixirSenseExample.ModuleWithTypespecs.Impl.some() + """ + + list = Suggestion.suggestions(buffer, 1, 50) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "params from macrocallback" do + buffer = """ + require ElixirSenseExample.ModuleWithTypespecs.MacroImpl + ElixirSenseExample.ModuleWithTypespecs.MacroImpl.some() + """ + + list = Suggestion.suggestions(buffer, 2, 55) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params" do + buffer = """ + defmodule Foo do + @spec some([{:foo, integer()} | {:bar, String.t()}]) :: :ok + def some(options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 6, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params macro" do + buffer = """ + defmodule Foo do + @spec some([{:foo, integer()} | {:bar, String.t()}]) :: Macro.t() + defmacro some(options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 6, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params multiple specs" do + buffer = """ + defmodule Foo do + @spec some([{:foo, integer()} | {:bar, String.t()}]) :: :ok + @spec some(nil) :: :ok + def some(options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 7, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params multiple functions" do + buffer = """ + defmodule Foo do + @spec some([{:foo, integer()}]) :: :ok + def some(options), do: :ok + + @spec some([{:bar, String.t()}]) :: :ok + def some(options, a), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 9, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params with default args" do + buffer = """ + defmodule Foo do + @spec some(atom, [{:foo, integer()} | {:bar, String.t()}]) :: :ok + def some(a \\\\ nil, options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 6, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params from callback" do + buffer = """ + defmodule Foo do + @callback some([{:foo, integer()} | {:bar, String.t()}]) :: :ok + end + + defmodule Bar do + @behaviour Foo + + @impl true + def some(options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 12, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params from macrocallback" do + buffer = """ + defmodule Foo do + @macrocallback some([{:foo, integer()} | {:bar, String.t()}]) :: Macro.t() + end + + defmodule Bar do + @behaviour Foo + + @impl true + defmacro some(options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 12, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params from compiled module callback" do + buffer = """ + defmodule Bar do + @behaviour ElixirSenseExample.ModuleWithTypespecs.Behaviour + + @impl true + def some(options), do: :ok + + def go do + some() + end + end + """ + + list = Suggestion.suggestions(buffer, 8, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) + end + + test "metadata params from compiled module macrocallback" do + buffer = """ + defmodule Bar do + @behaviour ElixirSenseExample.ModuleWithTypespecs.MacroBehaviour + + @impl true + defmacro some(options), do: :ok + + def go do + some() + end + end + """ - assert suggestion_by_name("large_o", buffer).expanded_spec == """ - @type large_t() :: - pid() - | port() - | (registered_name :: - atom()) - | {registered_name :: - atom(), node()}\ - """ + list = Suggestion.suggestions(buffer, 8, 10) + assert [%{name: "bar"}, %{name: "foo"}] = list |> Enum.filter(&(&1.type == :param_option)) end end diff --git a/apps/language_server/test/providers/definition/locator_test.exs b/apps/language_server/test/providers/definition/locator_test.exs index fc3a10190..a332f92db 100644 --- a/apps/language_server/test/providers/definition/locator_test.exs +++ b/apps/language_server/test/providers/definition/locator_test.exs @@ -1421,14 +1421,13 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.LocatorTest do end """ - assert Locator.definition(buffer, 8, 22) == %Location{ + assert %Location{ type: :function, file: nil, line: 3, column: 5, - end_line: 3, - end_column: 26 - } + end_line: 3 + } = Locator.definition(buffer, 8, 22) end end @@ -1571,14 +1570,13 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.LocatorTest do end """ - assert Locator.definition(buffer, 3, 6) == %Location{ + assert %Location{ type: :function, file: nil, line: 6, column: 3, - end_line: 6, - end_column: 26 - } + end_line: 6 + } = Locator.definition(buffer, 3, 6) end test "find definition of local functions with alias" do diff --git a/apps/language_server/test/providers/implementation/locator_test.exs b/apps/language_server/test/providers/implementation/locator_test.exs index 47505c96f..f5445e0c2 100644 --- a/apps/language_server/test/providers/implementation/locator_test.exs +++ b/apps/language_server/test/providers/implementation/locator_test.exs @@ -715,10 +715,9 @@ defmodule ElixirLS.LanguageServer.Providers.Implementation.LocatorTest do file: nil, line: 2, column: 3, - end_line: 2, - end_column: 34 + end_line: 2 } - ] == + ] = Locator.implementations(buffer, 6, 15) end diff --git a/apps/language_server/test/support/module_with_typespecs.ex b/apps/language_server/test/support/module_with_typespecs.ex index 27bc0bb0d..94ffd2772 100644 --- a/apps/language_server/test/support/module_with_typespecs.ex +++ b/apps/language_server/test/support/module_with_typespecs.ex @@ -189,5 +189,32 @@ defmodule ElixirSenseExample.ModuleWithTypespecs do IO.inspect(options) {:asd, [], nil} end + + @spec fun_with_default(atom, [{:foo, integer()} | {:bar, String.t()}]) :: :ok + def fun_with_default(a \\ nil, options), do: :ok + + @spec multiple_functions([{:foo, integer()}]) :: :ok + def multiple_functions(options), do: :ok + + @spec multiple_functions([{:bar, String.t()}]) :: :ok + def multiple_functions(options, a), do: :ok + end + + defmodule Behaviour do + @callback some([{:foo, integer()} | {:bar, String.t()}]) :: :ok + end + + defmodule Impl do + @behaviour Behaviour + def some(a), do: :ok + end + + defmodule MacroBehaviour do + @macrocallback some([{:foo, integer()} | {:bar, String.t()}]) :: Macro.t() + end + + defmodule MacroImpl do + @behaviour MacroBehaviour + defmacro some(a), do: :ok end end diff --git a/dep_versions.exs b/dep_versions.exs index e5c127f8c..f68f4795b 100644 --- a/dep_versions.exs +++ b/dep_versions.exs @@ -1,5 +1,5 @@ [ - elixir_sense: "6c293f27ea3afc4f5913a7ed03a07db0fa15a0a2", + elixir_sense: "99db60361b34cb952a767461dd46ed455d15e4cb", dialyxir_vendored: "f8f64cfb6797c518294687e7c03ae817bacbc6ee", jason_v: "f1c10fa9c445cb9f300266122ef18671054b2330", erl2ex_vendored: "073ac6b9a44282e718b6050c7b27cedf9217a12a", diff --git a/mix.lock b/mix.lock index e34586109..2c8be8336 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir_vendored": {:git, "https://github.com/elixir-lsp/dialyxir.git", "f8f64cfb6797c518294687e7c03ae817bacbc6ee", [ref: "f8f64cfb6797c518294687e7c03ae817bacbc6ee"]}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "6c293f27ea3afc4f5913a7ed03a07db0fa15a0a2", [ref: "6c293f27ea3afc4f5913a7ed03a07db0fa15a0a2"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "99db60361b34cb952a767461dd46ed455d15e4cb", [ref: "6c293f27ea3afc4f5913a7ed03a07db0fa15a0a2"]}, "erl2ex_vendored": {:git, "https://github.com/elixir-lsp/erl2ex.git", "073ac6b9a44282e718b6050c7b27cedf9217a12a", [ref: "073ac6b9a44282e718b6050c7b27cedf9217a12a"]}, "erlex_vendored": {:git, "https://github.com/elixir-lsp/erlex.git", "c0e448db27bcbb3f369861d13e3b0607ed37048d", [ref: "c0e448db27bcbb3f369861d13e3b0607ed37048d"]}, "jason_v": {:git, "https://github.com/elixir-lsp/jason.git", "f1c10fa9c445cb9f300266122ef18671054b2330", [ref: "f1c10fa9c445cb9f300266122ef18671054b2330"]},