From 16a76bab1a6f749aca808cc1544c5c8c8c26497a Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 8 Jun 2023 13:23:04 +0200 Subject: [PATCH 01/16] add failing test --- test/elixir_sense/suggestions_test.exs | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/elixir_sense/suggestions_test.exs b/test/elixir_sense/suggestions_test.exs index d3ff621e..99c6506c 100644 --- a/test/elixir_sense/suggestions_test.exs +++ b/test/elixir_sense/suggestions_test.exs @@ -1562,6 +1562,44 @@ defmodule ElixirSense.SuggestionsTest do ] end + test "lists builtin module attributes on incomplete code" do + buffer = """ + defmodule My do + def start_link(id) do + GenServer.start_link(__MODULE__, id, name: via_tuple(id)) + end + + @ + def init(id) do + {:ok, + %Some.Mod{ + id: id, + events: [], + version: 0 + }} + end + end + """ + + list = + ElixirSense.suggestions(buffer, 6, 4) + |> Enum.filter(fn s -> s.type == :attribute end) + + assert list == [ + %{name: "@macrocallback", type: :attribute}, + %{name: "@moduledoc", type: :attribute}, + %{name: "@myattr", type: :attribute} + ] + + list = + ElixirSense.suggestions(buffer, 5, 7) + |> Enum.filter(fn s -> s.type == :attribute end) + + assert list == [ + %{name: "@myattr", type: :attribute} + ] + end + test "lists doc snippets in module body" do buffer = """ defmodule MyModule do From dc7941b6625000febfad35220751a5d13f4a8dac Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 8 Jun 2023 13:24:06 +0200 Subject: [PATCH 02/16] avert crash on invalid attribute --- lib/elixir_sense/core/metadata_builder.ex | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index d9388db1..b08aeed1 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -689,20 +689,23 @@ defmodule ElixirSense.Core.MetadataBuilder do end defp pre({:@, [line: line, column: column] = meta_attr, [{name, meta, params}]}, state) do - {type, is_definition} = - case List.wrap(params) do + with {type, is_definition} <- (case List.wrap(params) do [] -> {nil, false} [param] -> {get_binding_type(state, param), true} - end - - state = - add_moduledoc_positions(state, [line: line, column: column], [{name, meta, params}], line) + _ -> :error + end) + do + state = + add_moduledoc_positions(state, [line: line, column: column], [{name, meta, params}], line) - new_ast = {:@, meta_attr, [{name, add_no_call(meta), params}]} - pre_module_attribute(new_ast, state, {line, column}, name, type, is_definition) + new_ast = {:@, meta_attr, [{name, add_no_call(meta), params}]} + pre_module_attribute(new_ast, state, {line, column}, name, type, is_definition) + else + _ -> {[], state} + end end # transform 1.2 alias/require/import/use syntax ast into regular From 874dcc5f5de748ebdd4a08d696c9cc25d41bc925 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 8 Jun 2023 13:24:22 +0200 Subject: [PATCH 03/16] include token_metadata --- lib/elixir_sense/core/parser.ex | 2 +- test/elixir_sense/core/metadata_builder_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir_sense/core/parser.ex b/lib/elixir_sense/core/parser.ex index b483aff1..1792b402 100644 --- a/lib/elixir_sense/core/parser.ex +++ b/lib/elixir_sense/core/parser.ex @@ -107,7 +107,7 @@ defmodule ElixirSense.Core.Parser do original_error \\ nil, opts \\ [] ) do - case Code.string_to_quoted(source, opts |> Keyword.put(:columns, true)) do + case Code.string_to_quoted(source, opts |> Keyword.merge(columns: true, token_metadata: true)) do {:ok, ast} -> {:ok, ast, source} diff --git a/test/elixir_sense/core/metadata_builder_test.exs b/test/elixir_sense/core/metadata_builder_test.exs index c560864a..d1c6ad5d 100644 --- a/test/elixir_sense/core/metadata_builder_test.exs +++ b/test/elixir_sense/core/metadata_builder_test.exs @@ -5497,7 +5497,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do defp string_to_state(string) do string - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> (fn {:ok, ast} -> ast end).() |> MetadataBuilder.build() end From 88f55106a7dc6a1cbe03b6691b643675f1390ba1 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 11 Jun 2023 08:32:29 +0200 Subject: [PATCH 04/16] exclude more from calls --- lib/elixir_sense/core/metadata_builder.ex | 2 +- test/elixir_sense/core/parser_test.exs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index b08aeed1..efdcdeca 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -32,7 +32,7 @@ defmodule ElixirSense.Core.MetadataBuilder do defguardp is_call(call, params) when is_atom(call) and is_list(params) and - call not in [:., :__aliases__, :"::", :{}, :|>] + call not in [:., :__aliases__, :"::", :{}, :|>, :%, :%{}] defguard is_call_meta_simple(list) when elem(hd(list), 0) == :line and elem(hd(tl(list)), 0) == :column diff --git a/test/elixir_sense/core/parser_test.exs b/test/elixir_sense/core/parser_test.exs index 615133d6..bf04b9c1 100644 --- a/test/elixir_sense/core/parser_test.exs +++ b/test/elixir_sense/core/parser_test.exs @@ -427,7 +427,7 @@ defmodule ElixirSense.Core.ParserTest do defmodule MyModule do def func() do %{ - data: "foo" + data: foo() } end end @@ -435,7 +435,7 @@ defmodule ElixirSense.Core.ParserTest do assert %ElixirSense.Core.Metadata{ calls: %{ - 3 => [%{func: :%{}}] + 4 => [%{func: :foo}] } } = parse_string(source, true, true, 4) end @@ -445,7 +445,7 @@ defmodule ElixirSense.Core.ParserTest do defmodule MyModule do def func() do %{ - data: "foo" + data: foo() end end @@ -453,7 +453,7 @@ defmodule ElixirSense.Core.ParserTest do assert %ElixirSense.Core.Metadata{ calls: %{ - 3 => [%{func: :%{}}] + 4 => [%{func: :foo}] } } = parse_string(source, true, true, 4) end From bccf37f9ee10871d6db8b23c8d1f2bd83d64e5de Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 11 Jun 2023 08:33:52 +0200 Subject: [PATCH 05/16] use token_metadata: true in parser --- lib/elixir_sense.ex | 2 +- lib/elixir_sense/core/metadata.ex | 2 +- lib/elixir_sense/core/metadata_builder.ex | 260 +++++--- lib/elixir_sense/core/state.ex | 7 +- .../core/metadata_builder_test.exs | 573 ++---------------- test/elixir_sense/core/parser_test.exs | 4 +- test/elixir_sense/definition_test.exs | 2 +- test/elixir_sense/suggestions_test.exs | 2 +- 8 files changed, 222 insertions(+), 630 deletions(-) diff --git a/lib/elixir_sense.ex b/lib/elixir_sense.ex index 4532ddf8..ad3d3d5a 100644 --- a/lib/elixir_sense.ex +++ b/lib/elixir_sense.ex @@ -492,7 +492,7 @@ defmodule ElixirSense do ...> end ...> ''' iex> ElixirSense.string_to_quoted(code, 1) - {:ok, {:defmodule, [line: 1, column: 1], [[do: {:__block__, [], []}]]}} + {:ok, {:defmodule, [do: [line: 1, column: 11], end: [line: 2, column: 1], line: 1, column: 1], [[do: {:__block__, [], []}]]}} """ @spec string_to_quoted(String.t(), pos_integer | nil, non_neg_integer, keyword) :: {:ok, Macro.t()} | {:error, {line :: pos_integer(), term(), term()}} diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index a51c7394..94c2ef54 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -80,7 +80,7 @@ defmodule ElixirSense.Core.Metadata do %State.ModFunInfo{positions: [{line, column}]} -> # Hacky :shrug line_offset = 1 - column_offset = -8 + column_offset = 2 {line + line_offset, column + column_offset} _ -> diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index efdcdeca..38588e5d 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -38,8 +38,15 @@ defmodule ElixirSense.Core.MetadataBuilder do when elem(hd(list), 0) == :line and elem(hd(tl(list)), 0) == :column defguard is_call_meta(list) - when (elem(hd(list), 0) == :no_parens and is_call_meta_simple(tl(list))) or - is_call_meta_simple(list) + when is_call_meta_simple(list) or + (elem(hd(list), 0) == :no_parens and is_call_meta_simple(tl(list))) or + (elem(hd(list), 0) == :closing and is_call_meta_simple(tl(list))) or + (elem(hd(list), 0) == :end_of_expression and elem(hd(tl(list)), 0) == :closing and + is_call_meta_simple(tl(tl(list)))) or + (elem(hd(list), 0) == :end_of_expression and elem(hd(tl(list)), 0) == :no_parens and + is_call_meta_simple(tl(tl(list)))) + + # defguard is_call_meta(list) when is_list(list) @doc """ Traverses the AST building/retrieving the environment information. @@ -77,9 +84,15 @@ defmodule ElixirSense.Core.MetadataBuilder do end end - defp pre_module(ast, state, {line, column} = position, module, types \\ [], functions \\ []) do + defp pre_module(ast, state, meta, module, types \\ [], functions \\ []) do module = normalize_module(module) + position = + {line, column} = { + Keyword.fetch!(meta, :line), + Keyword.fetch!(meta, :column) + } + state = state |> maybe_add_protocol_implementation(module) @@ -132,11 +145,11 @@ defmodule ElixirSense.Core.MetadataBuilder do |> result(ast) end - def pre_protocol(ast, state, position, module) do + def pre_protocol(ast, state, meta, module) do # protocol defines a type `@type t :: term` # and functions __protocol__/1, impl_for/1, impl_for!/1 - pre_module(ast, state, position, module, @protocol_types, @protocol_functions) + pre_module(ast, state, meta, module, @protocol_types, @protocol_functions) end def post_protocol(ast, state) do @@ -310,7 +323,9 @@ defmodule ElixirSense.Core.MetadataBuilder do |> result(ast) end - defp pre_clause({_clause, [line: line, column: _column], _} = ast, state, lhs) do + defp pre_clause({_clause, meta, _} = ast, state, lhs) do + line = meta |> Keyword.fetch!(:line) + vars = state |> find_vars(lhs, Enum.at(state.binding_context, 0)) @@ -431,50 +446,51 @@ defmodule ElixirSense.Core.MetadataBuilder do end defp pre( - {:defmodule, _, [{:__aliases__, [line: line, column: column], module}, _]} = ast, + {:defmodule, meta, [{:__aliases__, _, module}, _]} = ast, state ) do - pre_module(ast, state, {line, column}, module) + pre_module(ast, state, meta, module) end - defp pre({:defmodule, [line: line, column: column], [module, _]} = ast, state) + defp pre({:defmodule, meta, [module, _]} = ast, state) when is_atom(module) do - pre_module(ast, state, {line, column}, module) + pre_module(ast, state, meta, module) end defp pre( - {:defprotocol, _, [{:__aliases__, [line: line, column: column], module}, _]} = ast, + {:defprotocol, meta, [{:__aliases__, _, module}, _]} = ast, state ) do - pre_protocol(ast, state, {line, column}, module) + pre_protocol(ast, state, meta, module) end - defp pre({:defprotocol, [line: line, column: column], [module, _]} = ast, state) + defp pre({:defprotocol, meta, [module, _]} = ast, state) when is_atom(module) do - pre_protocol(ast, state, {line, column}, module) + pre_protocol(ast, state, meta, module) end defp pre( - {:defimpl, _, [{:__aliases__, [line: line, column: column], protocol}, impl_args | _]} = - ast, + {:defimpl, meta, [{:__aliases__, _, protocol}, impl_args | _]} = ast, state ) do - pre_protocol_implementation(ast, state, {line, column}, protocol, impl_args) + pre_protocol_implementation(ast, state, meta, protocol, impl_args) end defp pre( - {:defimpl, [line: line, column: column], [protocol, impl_args | _]} = ast, + {:defimpl, meta, [protocol, impl_args | _]} = ast, state ) when is_atom(protocol) do - pre_protocol_implementation(ast, state, {line, column}, protocol, impl_args) + pre_protocol_implementation(ast, state, meta, protocol, impl_args) end defp pre( - {:defdelegate, meta, [{name, [line: line, column: column] = meta2, params}, body]}, + {:defdelegate, meta, [{name, meta2, params}, body]}, state ) when is_atom(name) do + line = Keyword.fetch!(meta2, :line) + column = Keyword.fetch!(meta2, :column) ast_without_params = {:defdelegate, meta, [{name, add_no_call(meta2), []}, body]} target_module = body |> Keyword.get(:to) @@ -505,20 +521,23 @@ defmodule ElixirSense.Core.MetadataBuilder do # function head with guards defp pre( - {def_name, meta, - [{:when, _, [{name, [line: line, column: column] = meta2, params}, guards]}, body]}, + {def_name, meta, [{:when, _, [{name, meta2, params}, guards]}, body]}, state ) when def_name in @defs do + line = Keyword.fetch!(meta2, :line) + column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, guards, body]} pre_func(ast_without_params, state, %{line: line, col: column}, name, params) end defp pre( - {def_name, meta, [{name, [line: line, column: column] = meta2, params}, body]}, + {def_name, meta, [{name, meta2, params}, body]}, state ) when def_name in @defs and is_atom(name) do + line = Keyword.fetch!(meta2, :line) + column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, body]} pre_func(ast_without_params, state, %{line: line, col: column}, name, params) end @@ -527,41 +546,47 @@ defmodule ElixirSense.Core.MetadataBuilder do defp pre( {def_name, meta, [ - {:when, [line: _, column: _], - [{name, [line: line, column: column] = meta2, params}, body]} + {:when, _meta, [{name, meta2, params}, body]} ]}, state ) when def_name in @defs do + line = Keyword.fetch!(meta2, :line) + column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, body]} pre_func(ast_without_params, state, %{line: line, col: column}, name, params) end # function head - defp pre({def_name, meta, [{name, [line: line, column: column] = meta2, params}]}, state) + defp pre({def_name, meta, [{name, meta2, params}]}, state) when def_name in @defs and is_atom(name) do + line = Keyword.fetch!(meta2, :line) + column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, nil]} pre_func(ast_without_params, state, %{line: line, col: column}, name, params) end defp pre( - {:@, [line: line, column: _column], - [{:behaviour, _, [{:__aliases__, _, module_expression}]}]} = ast, + {:@, meta, [{:behaviour, _, [{:__aliases__, _, module_expression}]}]} = ast, state ) do + line = Keyword.fetch!(meta, :line) module = concat_module_expression(state, module_expression) pre_behaviour(ast, state, line, module) end - defp pre({:@, [line: line, column: _column], [{:behaviour, _, [erlang_module]}]} = ast, state) do + defp pre({:@, meta, [{:behaviour, _, [erlang_module]}]} = ast, state) do + line = Keyword.fetch!(meta, :line) pre_behaviour(ast, state, line, erlang_module) end # protocol derive defp pre( - {:@, [line: _line, column: _column] = position, [{:derive, _, [derived_protos]}]} = ast, + {:@, meta, [{:derive, _, [derived_protos]}]} = ast, state ) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) current_module_variants = state |> get_current_module_variants List.wrap(derived_protos) @@ -582,12 +607,12 @@ defmodule ElixirSense.Core.MetadataBuilder do case acc_1.mods_funs_to_positions[{mod_any, nil, nil}] do nil -> - # implemantation for: Any not detected (is in other file etc.) + # implementation for: Any not detected (is in other file etc.) acc_1 - |> add_module_to_index(mod, position) + |> add_module_to_index(mod, {line, column}) _any_mods_funs -> - # copy implemantation for: Any + # copy implementation for: Any copied_mods_funs_to_positions = for {{module, fun, arity}, val} <- acc_1.mods_funs_to_positions, module == mod_any, @@ -610,13 +635,16 @@ defmodule ElixirSense.Core.MetadataBuilder do end defp pre( - {:@, [line: line, column: column] = _meta_attr, + {:@, meta_attr, [{kind, _, [{:"::", _meta, _params = [{name, _, type_args}, _type_def]} = spec]}]} = ast, state ) when kind in [:type, :typep, :opaque] and is_atom(name) and (is_nil(type_args) or is_list(type_args)) do + line = Keyword.fetch!(meta_attr, :line) + column = Keyword.fetch!(meta_attr, :column) + pre_type( ast, state, @@ -629,7 +657,7 @@ defmodule ElixirSense.Core.MetadataBuilder do end defp pre( - {:@, [line: line, column: column] = _meta_attr, + {:@, meta_attr, [ {kind, _, [{:when, _, [{:"::", _meta, _params = [{name, _, type_args}, _type_def]}, _]} = spec]} @@ -638,6 +666,9 @@ defmodule ElixirSense.Core.MetadataBuilder do ) when kind in [:spec, :callback, :macrocallback] and is_atom(name) and (is_nil(type_args) or is_list(type_args)) do + line = Keyword.fetch!(meta_attr, :line) + column = Keyword.fetch!(meta_attr, :column) + pre_spec( ast, state, @@ -650,13 +681,16 @@ defmodule ElixirSense.Core.MetadataBuilder do end defp pre( - {:@, [line: line, column: column] = _meta_attr, + {:@, meta_attr, [{kind, _, [{:"::", _meta, _params = [{name, _, type_args}, _type_def]} = spec]}]} = ast, state ) when kind in [:spec, :callback, :macrocallback] and is_atom(name) and (is_nil(type_args) or is_list(type_args)) do + line = Keyword.fetch!(meta_attr, :line) + column = Keyword.fetch!(meta_attr, :column) + pre_spec( ast, state, @@ -671,12 +705,14 @@ defmodule ElixirSense.Core.MetadataBuilder do # incomplete spec # @callback my(integer) defp pre( - {:@, [line: line, column: column] = _meta_attr, - [{kind, _, [{name, _, type_args}]} = spec]} = ast, + {:@, meta_attr, [{kind, _, [{name, _, type_args}]} = spec]} = ast, state ) when kind in [:spec, :callback, :macrocallback] and is_atom(name) and (is_nil(type_args) or is_list(type_args)) do + line = Keyword.fetch!(meta_attr, :line) + column = Keyword.fetch!(meta_attr, :column) + pre_spec( ast, state, @@ -688,41 +724,45 @@ defmodule ElixirSense.Core.MetadataBuilder do ) end - defp pre({:@, [line: line, column: column] = meta_attr, [{name, meta, params}]}, state) do - with {type, is_definition} <- (case List.wrap(params) do - [] -> - {nil, false} + defp pre({:@, meta_attr, [{name, meta, params}]}, state) do + line = Keyword.fetch!(meta_attr, :line) + column = Keyword.fetch!(meta_attr, :column) - [param] -> - {get_binding_type(state, param), true} - _ -> :error - end) - do + with {type, is_definition} <- + (case List.wrap(params) do + [] -> + {nil, false} + + [param] -> + {get_binding_type(state, param), true} + + _ -> + :error + end) do state = add_moduledoc_positions(state, [line: line, column: column], [{name, meta, params}], line) new_ast = {:@, meta_attr, [{name, add_no_call(meta), params}]} pre_module_attribute(new_ast, state, {line, column}, name, type, is_definition) - else - _ -> {[], state} - end + else + _ -> {[], state} + end end # transform 1.2 alias/require/import/use syntax ast into regular defp pre( - {directive, [line: line, column: column], - [{{:., _, [prefix_expression, :{}]}, _, postfix_expressions} | _]}, + {directive, meta, [{{:., _, [prefix_expression, :{}]}, _, postfix_expressions} | _]}, state ) when directive in [:alias, :require, :import, :use] do directives = modules_from_12_syntax(state, postfix_expressions, prefix_expression) |> Enum.map(fn module_list -> - {directive, [line: line, column: column], [{:__aliases__, [], module_list}]} + {directive, meta, [{:__aliases__, [], module_list}]} end) state - |> result({:__block__, [line: line, column: column], directives}) + |> result({:__block__, meta, directives}) end # transform alias/require/import/use without options into with empty options @@ -733,26 +773,27 @@ defmodule ElixirSense.Core.MetadataBuilder do # import with options defp pre( - {:import, [line: line, column: _column], - [{:__aliases__, _, module_expression = [_ | _]}, opts]} = ast, + {:import, meta, [{:__aliases__, _, module_expression = [_ | _]}, opts]} = ast, state ) do + line = Keyword.fetch!(meta, :line) module = concat_module_expression(state, module_expression) pre_import(ast, state, line, module, opts) end # atom module - defp pre({:import, [line: line, column: _column], [atom, opts] = ast}, state) + defp pre({:import, meta, [atom, opts] = ast}, state) when is_atom(atom) do + line = Keyword.fetch!(meta, :line) pre_import(ast, state, line, atom, opts) end # require with `as` option defp pre( - {:require, [line: line, column: _column], - [{_, _, module_expression = [_ | _]}, [as: alias_expression]]} = ast, + {:require, meta, [{_, _, module_expression = [_ | _]}, [as: alias_expression]]} = ast, state ) do + line = Keyword.fetch!(meta, :line) module = concat_module_expression(state, module_expression) alias_tuple = alias_tuple(module, alias_expression) @@ -761,8 +802,9 @@ defmodule ElixirSense.Core.MetadataBuilder do end # require erlang module with `as` option - defp pre({:require, [line: line, column: _column], [mod, [as: alias_expression]]} = ast, state) + defp pre({:require, meta, [mod, [as: alias_expression]]} = ast, state) when is_atom(mod) do + line = Keyword.fetch!(meta, :line) alias_tuple = alias_tuple(mod, alias_expression) {_, new_state} = pre_alias(ast, state, line, alias_tuple) pre_require(ast, new_state, line, mod) @@ -770,25 +812,27 @@ defmodule ElixirSense.Core.MetadataBuilder do # require with options defp pre( - {:require, [line: line, column: _column], [{_, _, module_expression = [_ | _]}, _opts]} = - ast, + {:require, meta, [{_, _, module_expression = [_ | _]}, _opts]} = ast, state ) do + line = Keyword.fetch!(meta, :line) module = concat_module_expression(state, module_expression) pre_require(ast, state, line, module) end - defp pre({:require, [line: line, column: _column], [mod, _opts]} = ast, state) + defp pre({:require, meta, [mod, _opts]} = ast, state) when is_atom(mod) do + line = Keyword.fetch!(meta, :line) pre_require(ast, state, line, mod) end # alias with `as` option defp pre( - {:alias, [line: line, column: column], - [{_, _, module_expression = [_ | _]}, [as: alias_expression]]} = ast, + {:alias, meta, [{_, _, module_expression = [_ | _]}, [as: alias_expression]]} = ast, state ) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) module = concat_module_expression(state, module_expression) alias_tuple = alias_tuple(module, alias_expression) state = add_first_alias_positions(state, line, column) @@ -797,9 +841,11 @@ defmodule ElixirSense.Core.MetadataBuilder do # alias for __MODULE__ defp pre( - {:alias, [line: line, column: column], [{:__MODULE__, _, nil}, []]} = ast, + {:alias, meta, [{:__MODULE__, _, nil}, []]} = ast, state ) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) module = get_current_module(state) if module == Elixir do @@ -820,10 +866,11 @@ defmodule ElixirSense.Core.MetadataBuilder do # alias for submodule of __MODULE__ with `as` option defp pre( - {:alias, [line: line, column: column], [{:__MODULE__, _, nil}, [as: alias_expression]]} = - ast, + {:alias, meta, [{:__MODULE__, _, nil}, [as: alias_expression]]} = ast, state ) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) module = get_current_module(state) alias_tuple = alias_tuple(module, alias_expression) state = add_first_alias_positions(state, line, column) @@ -831,8 +878,10 @@ defmodule ElixirSense.Core.MetadataBuilder do end # alias atom module with `as` option - defp pre({:alias, [line: line, column: column], [mod, [as: alias_expression]]} = ast, state) + defp pre({:alias, meta, [mod, [as: alias_expression]]} = ast, state) when is_atom(mod) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) alias_tuple = alias_tuple(mod, alias_expression) state = add_first_alias_positions(state, line, column) pre_alias(ast, state, line, alias_tuple) @@ -840,10 +889,11 @@ defmodule ElixirSense.Core.MetadataBuilder do # alias defp pre( - {:alias, [line: line, column: column], - [{:__aliases__, _, module_expression = [_ | _]}, _opts]} = ast, + {:alias, meta, [{:__aliases__, _, module_expression = [_ | _]}, _opts]} = ast, state ) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) module = concat_module_expression(state, module_expression) alias_tuple = {Module.concat([List.last(module_expression)]), module} state = add_first_alias_positions(state, line, column) @@ -851,8 +901,11 @@ defmodule ElixirSense.Core.MetadataBuilder do end # alias atom module - defp pre({:alias, [line: line, column: column], [mod, _opts]} = ast, state) + defp pre({:alias, meta, [mod, _opts]} = ast, state) when is_atom(mod) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) + if Introspection.elixir_module?(mod) do alias_tuple = {Module.concat([List.last(Module.split(mod))]), mod} @@ -897,8 +950,9 @@ defmodule ElixirSense.Core.MetadataBuilder do {ast, state} end - defp pre({atom, [line: line, column: _column], [_ | _]} = ast, state) + defp pre({atom, meta, [_ | _]} = ast, state) when atom in @scope_keywords do + line = Keyword.fetch!(meta, :line) pre_scope_keyword(ast, state, line) end @@ -918,13 +972,16 @@ defmodule ElixirSense.Core.MetadataBuilder do pre_clause({:->, meta, [:_, rhs]}, state, lhs) end - defp pre({atom, [line: _line, column: _column] = meta, [lhs, rhs]}, state) + defp pre({atom, meta, [lhs, rhs]}, state) when atom in [:=, :<-] do result(state, {atom, meta, [lhs, rhs]}) end - defp pre({var_or_call, [line: line, column: column], nil} = ast, state) + defp pre({var_or_call, meta, nil} = ast, state) when is_atom(var_or_call) and var_or_call != :__MODULE__ do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) + if Enum.any?(get_current_vars(state), &(&1.name == var_or_call)) do vars = state @@ -935,7 +992,11 @@ defmodule ElixirSense.Core.MetadataBuilder do else # pre Elixir 1.4 local call syntax # TODO remove on Elixir 2.0 - add_call_to_line(state, {nil, var_or_call, 0}, {line, column}) + if is_call_meta(meta) do + add_call_to_line(state, {nil, var_or_call, 0}, {line, column}) + else + state + end end |> add_current_env_to_line(line) |> result(ast) @@ -968,8 +1029,11 @@ defmodule ElixirSense.Core.MetadataBuilder do {expanded_ast, state} end - defp pre({type, [line: line, column: column], fields} = ast, state) + defp pre({type, meta, fields} = ast, state) when type in [:defstruct, :defexception] do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) + fields = case fields do [fields] when is_list(fields) -> @@ -1157,13 +1221,18 @@ defmodule ElixirSense.Core.MetadataBuilder do end # Any other tuple with a line - defp pre({_, [line: line, column: _column], _} = ast, state) do - state - |> add_current_env_to_line(line) - |> result(ast) + defp pre({_, meta, _} = ast, state) do + case Keyword.get(meta, :line) do + nil -> + {ast, state} + + line -> + state + |> add_current_env_to_line(line) + |> result(ast) + end end - # No line defined defp pre(ast, state) do {ast, state} end @@ -1193,13 +1262,14 @@ defmodule ElixirSense.Core.MetadataBuilder do post_module(ast, state) end - defp post({def_name, [line: _line, column: _column], [{name, _, _params}, _]} = ast, state) + defp post({def_name, meta, [{name, _, _params}, _]} = ast, state) when def_name in @defs and is_atom(name) do + line = Keyword.fetch!(meta, :line) post_func(ast, state) end defp post( - {def_name, [line: _line, column: _column], [{name, _, _params}, _guards, _]} = ast, + {def_name, meta, [{name, _, _params}, _guards, _]} = ast, state ) when def_name in @defs and is_atom(name) do @@ -1283,12 +1353,13 @@ defmodule ElixirSense.Core.MetadataBuilder do post_block_keyword(ast, state) end - defp post({:->, [line: _line, column: _column], [_lhs, _rhs]} = ast, state) do + defp post({:->, _meta, [_lhs, _rhs]} = ast, state) do post_clause(ast, state) end - defp post({atom, [line: line, column: _column], [lhs, rhs]} = ast, state) + defp post({atom, meta, [lhs, rhs]} = ast, state) when atom in [:=, :<-] do + line = Keyword.fetch!(meta, :line) match_context_r = get_binding_type(state, rhs) match_context_r = @@ -1347,7 +1418,9 @@ defmodule ElixirSense.Core.MetadataBuilder do defp find_vars(state, ast, match_context \\ nil) - defp find_vars(_state, {var, [line: line, column: column], nil}, :rescue) when is_atom(var) do + defp find_vars(_state, {var, meta, nil}, :rescue) when is_atom(var) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) match_context = {:struct, [], {:atom, Exception}, nil} [%VarInfo{name: var, positions: [{line, column}], type: match_context, is_definition: true}] end @@ -1410,20 +1483,25 @@ defmodule ElixirSense.Core.MetadataBuilder do defp match_var( _state, - {:^, _meta, [{var, [line: line, column: column], nil}]}, + {:^, _meta, [{var, meta, nil}]}, {vars, match_context} = ast ) when is_atom(var) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) var_info = %VarInfo{name: var, positions: [{line, column}], type: match_context} {ast, {[var_info | vars], nil}} end defp match_var( _state, - {var, [line: line, column: column], nil} = ast, + {var, meta, nil} = ast, {vars, match_context} ) when is_atom(var) do + line = Keyword.fetch!(meta, :line) + column = Keyword.fetch!(meta, :column) + var_info = %VarInfo{ name: var, positions: [{line, column}], @@ -1712,13 +1790,13 @@ defmodule ElixirSense.Core.MetadataBuilder do defp pre_protocol_implementation( ast, state, - position, + meta, protocol, for_expression ) do implementations = get_implementations_from_for_expression(state, for_expression) - pre_module(ast, state, position, {protocol, implementations}, [], [{:__impl__, [:atom], :def}]) + pre_module(ast, state, meta, {protocol, implementations}, [], [{:__impl__, [:atom], :def}]) end defp get_implementations_from_for_expression(state, for: for_expression) do diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index 53341107..fba72359 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -489,7 +489,8 @@ defmodule ElixirSense.Core.State do params, type, options \\ [] - ) do + ) + when is_tuple(position) do current_info = Map.get(state.mods_funs_to_positions, {module, fun, arity}, %ModFunInfo{}) current_params = current_info |> Map.get(:params, []) current_positions = current_info |> Map.get(:positions, []) @@ -686,7 +687,7 @@ defmodule ElixirSense.Core.State do %__MODULE__{state | scopes: tl(state.scopes)} end - def add_current_module_to_index(%__MODULE__{} = state, position) do + def add_current_module_to_index(%__MODULE__{} = state, position) when is_tuple(position) do current_module_variants = get_current_module_variants(state) current_module_variants @@ -696,7 +697,7 @@ defmodule ElixirSense.Core.State do end) end - def add_module_to_index(%__MODULE__{} = state, module, position) do + def add_module_to_index(%__MODULE__{} = state, module, position) when is_tuple(position) do # TODO :defprotocol, :defimpl? add_mod_fun_to_position(state, {module, nil, nil}, position, nil, :defmodule) end diff --git a/test/elixir_sense/core/metadata_builder_test.exs b/test/elixir_sense/core/metadata_builder_test.exs index d1c6ad5d..dc35f5e7 100644 --- a/test/elixir_sense/core/metadata_builder_test.exs +++ b/test/elixir_sense/core/metadata_builder_test.exs @@ -2779,177 +2779,34 @@ defmodule ElixirSense.Core.MetadataBuilderTest do assert %{ {Enumerable.MyOtherStruct, nil, nil} => %ModFunInfo{ params: [nil], - positions: [[line: 17, column: 3]], type: :defmodule }, - {MyOtherStruct, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{16, 11}], - type: :defmodule - }, - {MyStruct, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{9, 11}], - type: :defmodule - }, - {Proto, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{1, 13}], - type: :defmodule - }, - {Proto, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 2, column: 15], nil}]], - positions: [{2, 7}], - type: :def - }, - {Proto, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 2, column: 15], nil}]], - positions: [{2, 7}], - type: :def - }, {Proto.Any, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{5, 9}], type: :defmodule }, - {Proto.Any, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 6, column: 15], nil}]], - positions: [{6, 7}], - type: :def - }, - {Proto.Any, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 6, column: 15], nil}]], - positions: [{6, 7}], - type: :def - }, {Proto.MyOtherStruct, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{5, 9}], type: :defmodule }, {Proto.MyOtherStruct, :reverse, 1} => %ModFunInfo{ params: [[{:term, [line: 6, column: 15], nil}]], - positions: [{6, 7}], type: :def }, {Proto.MyOtherStruct, :reverse, nil} => %ModFunInfo{ params: [[{:term, [line: 6, column: 15], nil}]], - positions: [{6, 7}], type: :def }, {Proto.MyStruct, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{5, 9}], type: :defmodule }, {Proto.MyStruct, :reverse, 1} => %ModFunInfo{ params: [[{:term, [line: 6, column: 15], nil}]], - positions: [{6, 7}], type: :def }, {Proto.MyStruct, :reverse, nil} => %ModFunInfo{ params: [[{:term, [line: 6, column: 15], nil}]], - positions: [{6, 7}], - type: :def - }, - {MyOtherStruct, :__struct__, 0} => %ModFunInfo{ - params: [[]], - positions: [{18, 3}], - type: :def - }, - {MyOtherStruct, :__struct__, 1} => %ModFunInfo{ - params: [[{:kv, [line: 18, column: 3], nil}]], - positions: [{18, 3}], - type: :def - }, - {MyOtherStruct, :__struct__, nil} => %ModFunInfo{ - params: [[{:kv, [line: 18, column: 3], nil}], []], - positions: [{18, 3}, {18, 3}], - type: :def - }, - {MyStruct, :__struct__, 0} => %ModFunInfo{ - params: [[]], - positions: [{11, 3}], - type: :def - }, - {MyStruct, :__struct__, 1} => %ModFunInfo{ - params: [[{:kv, [line: 11, column: 3], nil}]], - positions: [{11, 3}], - type: :def - }, - {MyStruct, :__struct__, nil} => %ModFunInfo{ - params: [[{:kv, [line: 11, column: 3], nil}], []], - positions: [{11, 3}, {11, 3}], - type: :def - }, - {Proto, :__protocol__, 1} => %ModFunInfo{ - params: [[{:atom, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto, :__protocol__, nil} => %ModFunInfo{ - params: [[{:atom, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto, :impl_for, 1} => %ModFunInfo{ - params: [[{:data, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto, :impl_for, nil} => %ModFunInfo{ - params: [[{:data, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto, :impl_for!, 1} => %ModFunInfo{ - params: [[{:data, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto, :impl_for!, nil} => %ModFunInfo{ - params: [[{:data, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto.MyStruct, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 5, column: 9], nil}]], - positions: [{5, 9}], - type: :def - }, - {Proto.Any, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 5, column: 9], nil}]], - positions: [{5, 9}], - type: :def - }, - {Proto.MyOtherStruct, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 5, column: 9], nil}]], - positions: [{5, 9}], - type: :def - }, - {Proto.MyOtherStruct, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 5, column: 9], nil}]], - positions: [{5, 9}], - type: :def - }, - {Proto.MyStruct, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 5, column: 9], nil}]], - positions: [{5, 9}], - type: :def - }, - {Proto.Any, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 5, column: 9], nil}]], - positions: [{5, 9}], - type: :def - }, - {Proto, :behaviour_info, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 1, column: 13], nil}]], - positions: [{1, 13}], - type: :def - }, - {Proto, :behaviour_info, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 1, column: 13], nil}]], - positions: [{1, 13}], type: :def } } = state.mods_funs_to_positions @@ -3052,7 +2909,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do assert %{ {OuterModule, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{2, 11}], + positions: [{2, 1}], type: :defmodule }, {OuterModule.InnerModule, :func, 0} => %ModFunInfo{ @@ -3065,14 +2922,9 @@ defmodule ElixirSense.Core.MetadataBuilderTest do positions: [{5, 9}], type: :def }, - {OuterModule.InnerModule, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{4, 13}], - type: :defmodule - }, {Impls, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{28, 11}], + positions: [{28, 1}], type: :defmodule }, {Reversible, :reverse, 1} => %ModFunInfo{ @@ -3080,134 +2932,14 @@ defmodule ElixirSense.Core.MetadataBuilderTest do positions: [{19, 7}], type: :def }, - {Reversible, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 19, column: 15], nil}]], - positions: [{19, 7}], - type: :def - }, - {Reversible, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{18, 13}], - type: :defmodule - }, - {Reversible.Map, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{31, 11}], - type: :defmodule - }, - {Reversible.Map, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 32, column: 17], nil}]], - positions: [{32, 9}], - type: :def - }, - {Reversible.Map, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 32, column: 17], nil}]], - positions: [{32, 9}], - type: :def - }, - {Reversible.My.List, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{31, 11}], - type: :defmodule - }, - {Reversible.My.List, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 32, column: 17], nil}]], - positions: [{32, 9}], - type: :def - }, - {Reversible.My.List, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 32, column: 17], nil}]], - positions: [{32, 9}], - type: :def - }, - {Reversible.String, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{23, 9}], - type: :defmodule - }, - {Reversible.String, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 24, column: 15], nil}]], - positions: [{24, 7}], - type: :def - }, - {Reversible.String, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 24, column: 15], nil}]], - positions: [{24, 7}], - type: :def - }, - {Some.Nested, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{14, 11}], - type: :defmodule - }, - {Reversible, :__protocol__, 1} => %ModFunInfo{ - params: [[{:atom, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible, :__protocol__, nil} => %ModFunInfo{ - params: [[{:atom, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible, :impl_for, 1} => %ModFunInfo{ - params: [[{:data, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible, :impl_for, nil} => %ModFunInfo{ - params: [[{:data, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible, :impl_for!, 1} => %ModFunInfo{ - params: [[{:data, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible, :impl_for!, nil} => %ModFunInfo{ - params: [[{:data, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible.Map, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 31, column: 11], nil}]], - positions: [{31, 11}], - type: :def - }, - {Reversible.Map, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 31, column: 11], nil}]], - positions: [{31, 11}], - type: :def - }, - {Reversible.My.List, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 31, column: 11], nil}]], - positions: [{31, 11}], - type: :def - }, - {Reversible.My.List, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 31, column: 11], nil}]], - positions: [{31, 11}], - type: :def - }, {Reversible.String, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 23, column: 9], nil}]], - positions: [{23, 9}], - type: :def - }, - {Reversible.String, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 23, column: 9], nil}]], - positions: [{23, 9}], + params: [[{:atom, [line: 23, column: 1], nil}]], + positions: [{23, 1}], type: :def }, {Reversible, :behaviour_info, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 18, column: 13], nil}]], - positions: [{18, 13}], - type: :def - }, - {Reversible, :behaviour_info, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 18, column: 13], nil}]], - positions: [{18, 13}], + params: [[{:atom, [line: 18, column: 1], nil}]], + positions: [{18, 1}], type: :def } } = state.mods_funs_to_positions @@ -3556,92 +3288,92 @@ defmodule ElixirSense.Core.MetadataBuilderTest do }, {MyModuleWithFuns, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{3, 11}], + positions: [{3, 1}], type: :defmodule }, {MyModuleWithFuns.Nested, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{19, 13}], + positions: [{19, 3}], type: :defmodule }, {MyModuleWithoutFuns, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{1, 11}], + positions: [{1, 1}], type: :defmodule }, {MyModuleWithFuns, :__info__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 3, column: 11], nil}]], - positions: [{3, 11}], + params: [[{:atom, [line: 3, column: 1], nil}]], + positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :__info__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 3, column: 11], nil}]], - positions: [{3, 11}], + params: [[{:atom, [line: 3, column: 1], nil}]], + positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :module_info, 0} => %ElixirSense.Core.State.ModFunInfo{ params: [[]], - positions: [{3, 11}], + positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :module_info, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 3, column: 11], nil}]], - positions: [{3, 11}], + params: [[{:atom, [line: 3, column: 1], nil}]], + positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :module_info, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 3, column: 11], nil}], []], - positions: [{3, 11}, {3, 11}], + params: [[{:atom, [line: 3, column: 1], nil}], []], + positions: [{3, 1}, {3, 1}], type: :def }, {MyModuleWithFuns.Nested, :__info__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 19, column: 13], nil}]], - positions: [{19, 13}], + params: [[{:atom, [line: 19, column: 3], nil}]], + positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :__info__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 19, column: 13], nil}]], - positions: [{19, 13}], + params: [[{:atom, [line: 19, column: 3], nil}]], + positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :module_info, 0} => %ElixirSense.Core.State.ModFunInfo{ params: [[]], - positions: [{19, 13}], + positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :module_info, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 19, column: 13], nil}]], - positions: [{19, 13}], + params: [[{:atom, [line: 19, column: 3], nil}]], + positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :module_info, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 19, column: 13], nil}], []], - positions: [{19, 13}, {19, 13}], + params: [[{:atom, [line: 19, column: 3], nil}], []], + positions: [{19, 3}, {19, 3}], type: :def }, {MyModuleWithoutFuns, :__info__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 1, column: 11], nil}]], - positions: [{1, 11}], + params: [[{:atom, [line: 1, column: 1], nil}]], + positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :__info__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 1, column: 11], nil}]], - positions: [{1, 11}], + params: [[{:atom, [line: 1, column: 1], nil}]], + positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :module_info, 0} => %ElixirSense.Core.State.ModFunInfo{ params: [[]], - positions: [{1, 11}], + positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :module_info, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 1, column: 11], nil}]], - positions: [{1, 11}], + params: [[{:atom, [line: 1, column: 1], nil}]], + positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :module_info, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 1, column: 11], nil}], []], - positions: [{1, 11}, {1, 11}], + params: [[{:atom, [line: 1, column: 1], nil}], []], + positions: [{1, 1}, {1, 1}], type: :def } } == state.mods_funs_to_positions @@ -3758,228 +3490,9 @@ defmodule ElixirSense.Core.MetadataBuilderTest do """ |> string_to_state - assert %{ - {Impls, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{33, 11}], - type: :defmodule - }, - {MyModuleWithFuns, :func, 0} => %ModFunInfo{ - params: [[]], - positions: [{4, 7}], - type: :def - }, - {MyModuleWithFuns, :func, nil} => %ModFunInfo{ - params: [[]], - positions: [{4, 7}], - type: :def - }, - {MyModuleWithFuns, :func_delegated, 1} => %ModFunInfo{ - params: [[{:par, [line: 18, column: 30], nil}]], - positions: [{18, 15}], - type: :defdelegate - }, - {MyModuleWithFuns, :func_delegated, nil} => %ModFunInfo{ - params: [[{:par, [line: 18, column: 30], nil}]], - positions: [{18, 15}], - type: :defdelegate - }, - {MyModuleWithFuns, :funcp, 0} => %ModFunInfo{ - params: [[]], - positions: [{7, 8}], - type: :defp - }, - {MyModuleWithFuns, :funcp, nil} => %ModFunInfo{ - params: [[]], - positions: [{7, 8}], - type: :defp - }, - {MyModuleWithFuns, :is_even, 1} => %ModFunInfo{ - params: [[{:value, [line: 16, column: 20], nil}]], - positions: [{16, 12}], - type: :defguard - }, - {MyModuleWithFuns, :is_even, nil} => %ModFunInfo{ - params: [[{:value, [line: 16, column: 20], nil}]], - positions: [{16, 12}], - type: :defguard - }, - {MyModuleWithFuns, :is_evenp, 1} => %ModFunInfo{ - params: [[{:value, [line: 17, column: 22], nil}]], - positions: [{17, 13}], - type: :defguardp - }, - {MyModuleWithFuns, :is_evenp, nil} => %ModFunInfo{ - params: [[{:value, [line: 17, column: 22], nil}]], - positions: [{17, 13}], - type: :defguardp - }, - {MyModuleWithFuns, :macro1, 1} => %ModFunInfo{ - params: [[{:ast, [line: 10, column: 19], nil}]], - positions: [{10, 12}], - type: :defmacro - }, - {MyModuleWithFuns, :macro1, nil} => %ModFunInfo{ - params: [[{:ast, [line: 10, column: 19], nil}]], - positions: [{10, 12}], - type: :defmacro - }, - {MyModuleWithFuns, :macro1p, 1} => %ModFunInfo{ - params: [[{:ast, [line: 13, column: 21], nil}]], - positions: [{13, 13}], - type: :defmacrop - }, - {MyModuleWithFuns, :macro1p, nil} => %ModFunInfo{ - params: [[{:ast, [line: 13, column: 21], nil}]], - positions: [{13, 13}], - type: :defmacrop - }, - {MyModuleWithFuns, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{3, 11}], - type: :defmodule - }, - {MyModuleWithFuns.Nested, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{19, 13}], - type: :defmodule - }, - {MyModuleWithoutFuns, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{1, 11}], - type: :defmodule - }, - {Reversible, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{23, 13}], - type: :defmodule - }, - {Reversible, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 24, column: 15], nil}]], - positions: [{24, 7}], - type: :def - }, - {Reversible, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 24, column: 15], nil}]], - positions: [{24, 7}], - type: :def - }, - {Reversible.Map, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{36, 11}], - type: :defmodule - }, - {Reversible.Map, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 37, column: 17], nil}]], - positions: [{37, 9}], - type: :def - }, - {Reversible.Map, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 37, column: 17], nil}]], - positions: [{37, 9}], - type: :def - }, - {Reversible.My.List, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{36, 11}], - type: :defmodule - }, - {Reversible.My.List, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 37, column: 17], nil}]], - positions: [{37, 9}], - type: :def - }, - {Reversible.My.List, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 37, column: 17], nil}]], - positions: [{37, 9}], - type: :def - }, - {Reversible.String, nil, nil} => %ModFunInfo{ - params: [nil], - positions: [{28, 9}], - type: :defmodule - }, - {Reversible.String, :reverse, 1} => %ModFunInfo{ - params: [[{:term, [line: 29, column: 15], nil}]], - positions: [{29, 7}], - type: :def - }, - {Reversible.String, :reverse, nil} => %ModFunInfo{ - params: [[{:term, [line: 29, column: 15], nil}]], - positions: [{29, 7}], - type: :def - }, - {Reversible, :impl_for!, nil} => %ModFunInfo{ - params: [[{:data, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible, :__protocol__, nil} => %ModFunInfo{ - params: [[{:atom, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible, :impl_for!, 1} => %ModFunInfo{ - params: [[{:data, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible, :impl_for, nil} => %ModFunInfo{ - params: [[{:data, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible, :__protocol__, 1} => %ModFunInfo{ - params: [[{:atom, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible, :impl_for, 1} => %ModFunInfo{ - params: [[{:data, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible.Map, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 36, column: 11], nil}]], - positions: [{36, 11}], - type: :def - }, - {Reversible.String, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 28, column: 9], nil}]], - positions: [{28, 9}], - type: :def - }, - {Reversible.Map, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 36, column: 11], nil}]], - positions: [{36, 11}], - type: :def - }, - {Reversible.String, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 28, column: 9], nil}]], - positions: [{28, 9}], - type: :def - }, - {Reversible.My.List, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 36, column: 11], nil}]], - positions: [{36, 11}], - type: :def - }, - {Reversible.My.List, :__impl__, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 36, column: 11], nil}]], - positions: [{36, 11}], - type: :def - }, - {Reversible, :behaviour_info, 1} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - }, - {Reversible, :behaviour_info, nil} => %ElixirSense.Core.State.ModFunInfo{ - params: [[{:atom, [line: 23, column: 13], nil}]], - positions: [{23, 13}], - type: :def - } - } = state.mods_funs_to_positions + assert Map.has_key?(state.mods_funs_to_positions, {Impls, :__info__, 1}) + assert Map.has_key?(state.mods_funs_to_positions, {Reversible, :__protocol__, 1}) + assert Map.has_key?(state.mods_funs_to_positions, {Reversible.My.List, :__impl__, 1}) end test "first_alias_positions" do @@ -4074,7 +3587,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do {InheritMod, :handle_call, nil} => %ModFunInfo{}, {InheritMod, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{1, 11}], + positions: [{1, 1}], type: :defmodule }, {InheritMod, :private_func, 0} => %ModFunInfo{ @@ -4398,7 +3911,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do }, {MyStruct, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{1, 11}], + positions: [{1, 1}], type: :defmodule } } = state.mods_funs_to_positions @@ -5069,14 +4582,14 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [[]], kind: :type, name: :t, - positions: [{1, 13}], + positions: [{1, 1}], specs: ["@type t :: term"] }, {Proto, :t, 0} => %ElixirSense.Core.State.TypeInfo{ args: [[]], kind: :type, name: :t, - positions: [{1, 13}], + positions: [{1, 1}], specs: ["@type t :: term"] } } diff --git a/test/elixir_sense/core/parser_test.exs b/test/elixir_sense/core/parser_test.exs index bf04b9c1..601b5611 100644 --- a/test/elixir_sense/core/parser_test.exs +++ b/test/elixir_sense/core/parser_test.exs @@ -15,7 +15,7 @@ defmodule ElixirSense.Core.ParserTest do assert %Metadata{ error: nil, - mods_funs_to_positions: %{{MyModule, nil, nil} => %{positions: [{1, 11}]}}, + mods_funs_to_positions: %{{MyModule, nil, nil} => %{positions: [{1, 1}]}}, lines_to_env: %{ 1 => %Env{imports: [{Kernel, []}]}, 3 => %Env{imports: [{Kernel, []}, {List, []}]} @@ -352,7 +352,7 @@ defmodule ElixirSense.Core.ParserTest do assert %Metadata{ error: nil, - mods_funs_to_positions: %{{MyModule, nil, nil} => %{positions: [{1, 11}]}}, + mods_funs_to_positions: %{{MyModule, nil, nil} => %{positions: [{1, 1}]}}, lines_to_env: %{ 1 => %Env{imports: [{Kernel, []}]}, 3 => %Env{imports: [{Kernel, []}, {List, []}]} diff --git a/test/elixir_sense/definition_test.exs b/test/elixir_sense/definition_test.exs index 2dd7673b..c4ff9bcd 100644 --- a/test/elixir_sense/definition_test.exs +++ b/test/elixir_sense/definition_test.exs @@ -910,7 +910,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :module, file: nil, line: 2, - column: 13 + column: 3 } end diff --git a/test/elixir_sense/suggestions_test.exs b/test/elixir_sense/suggestions_test.exs index 99c6506c..bc2d2e93 100644 --- a/test/elixir_sense/suggestions_test.exs +++ b/test/elixir_sense/suggestions_test.exs @@ -1568,7 +1568,7 @@ defmodule ElixirSense.SuggestionsTest do def start_link(id) do GenServer.start_link(__MODULE__, id, name: via_tuple(id)) end - + @ def init(id) do {:ok, From 3138145ce2d954653adaf4732d5612aced27f0fe Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Mon, 12 Jun 2023 06:30:20 +0200 Subject: [PATCH 06/16] register end positions for modules and functions --- lib/elixir_sense.ex | 4 +- lib/elixir_sense/core/metadata_builder.ex | 91 ++++++++----- lib/elixir_sense/core/state.ex | 48 ++++++- .../core/metadata_builder/alias_test.exs | 6 +- .../core/metadata_builder/import_test.exs | 6 +- .../core/metadata_builder/require_test.exs | 6 +- .../core/metadata_builder_test.exs | 124 +++++++----------- test/elixir_sense/definition_test.exs | 20 +-- 8 files changed, 169 insertions(+), 136 deletions(-) diff --git a/lib/elixir_sense.ex b/lib/elixir_sense.ex index ad3d3d5a..e2fb8c45 100644 --- a/lib/elixir_sense.ex +++ b/lib/elixir_sense.ex @@ -101,7 +101,7 @@ defmodule ElixirSense do ...> ''' iex> %{file: path, line: line, column: column} = ElixirSense.definition(code, 3, 11) iex> "#{Path.basename(path)}:#{to_string(line)}:#{to_string(column)}" - "module_with_functions.ex:6:7" + "module_with_functions.ex:6:3" """ @spec definition(String.t(), pos_integer, pos_integer) :: Location.t() | nil def definition(code, line, column) do @@ -143,7 +143,7 @@ defmodule ElixirSense do ...> ''' iex> [%{file: path, line: line, column: column}, _] = ElixirSense.implementations(code, 1, 37) |> Enum.sort iex> "#{Path.basename(path)}:#{to_string(line)}:#{to_string(column)}" - "example_protocol.ex:7:7" + "example_protocol.ex:7:3" """ @spec implementations(String.t(), pos_integer, pos_integer) :: [Location.t()] def implementations(code, line, column) do diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index 38588e5d..dc2a0ce0 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -87,17 +87,13 @@ defmodule ElixirSense.Core.MetadataBuilder do defp pre_module(ast, state, meta, module, types \\ [], functions \\ []) do module = normalize_module(module) - position = - {line, column} = { - Keyword.fetch!(meta, :line), - Keyword.fetch!(meta, :column) - } + {position = {line, column}, end_position} = extract_range(meta) state = state |> maybe_add_protocol_implementation(module) |> add_namespace(module) - |> add_current_module_to_index(position) + |> add_current_module_to_index(position, end_position) |> alias_submodule(module) |> new_alias_scope |> new_attributes_scope @@ -124,6 +120,7 @@ defmodule ElixirSense.Core.MetadataBuilder do name, mapped_args, position, + nil, kind ) end) @@ -225,25 +222,57 @@ defmodule ElixirSense.Core.MetadataBuilder do post_module(ast, state) end - defp pre_func({type, _, _} = ast, state, %{line: line, col: col}, name, params, options \\ []) + defp pre_func({type, _, _} = ast, state, meta, name, params, options \\ []) when is_atom(name) do vars = state |> find_vars(params) |> merge_same_name_vars() + {position, end_position} = extract_range(meta) + state |> new_named_func(name, length(params || [])) - |> add_func_to_index(name, params || [], {line, col}, type, options) + |> add_func_to_index(name, params || [], position, end_position, type, options) |> new_alias_scope |> new_import_scope |> new_require_scope |> new_func_vars_scope |> add_vars(vars, true) - |> add_current_env_to_line(line) + |> add_current_env_to_line(Keyword.fetch!(meta, :line)) |> result(ast) end + defp extract_range(meta) do + position = { + Keyword.fetch!(meta, :line), + Keyword.fetch!(meta, :column) + } + + end_position = + case meta[:end] do + nil -> + case meta[:end_of_expression] do + nil -> + nil + + end_of_expression_meta -> + { + Keyword.fetch!(end_of_expression_meta, :line), + Keyword.fetch!(end_of_expression_meta, :column) + } + end + + end_meta -> + { + Keyword.fetch!(end_meta, :line), + Keyword.fetch!(end_meta, :column) + 3 + } + end + + {position, end_position} + end + defp post_func(ast, state) do state |> remove_alias_scope @@ -424,6 +453,7 @@ defmodule ElixirSense.Core.MetadataBuilder do :behaviour_info, [{:atom, [line: line, column: column], nil}], pos, + nil, :def ) else @@ -489,8 +519,6 @@ defmodule ElixirSense.Core.MetadataBuilder do state ) when is_atom(name) do - line = Keyword.fetch!(meta2, :line) - column = Keyword.fetch!(meta2, :column) ast_without_params = {:defdelegate, meta, [{name, add_no_call(meta2), []}, body]} target_module = body |> Keyword.get(:to) @@ -509,7 +537,7 @@ defmodule ElixirSense.Core.MetadataBuilder do _ -> [] end - pre_func(ast_without_params, state, %{line: line, col: column}, name, params, options) + pre_func(ast_without_params, state, meta, name, params, options) end # quote do @@ -525,10 +553,8 @@ defmodule ElixirSense.Core.MetadataBuilder do state ) when def_name in @defs do - line = Keyword.fetch!(meta2, :line) - column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, guards, body]} - pre_func(ast_without_params, state, %{line: line, col: column}, name, params) + pre_func(ast_without_params, state, meta, name, params) end defp pre( @@ -536,10 +562,8 @@ defmodule ElixirSense.Core.MetadataBuilder do state ) when def_name in @defs and is_atom(name) do - line = Keyword.fetch!(meta2, :line) - column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, body]} - pre_func(ast_without_params, state, %{line: line, col: column}, name, params) + pre_func(ast_without_params, state, meta, name, params) end # defguard and defguardp @@ -551,19 +575,15 @@ defmodule ElixirSense.Core.MetadataBuilder do state ) when def_name in @defs do - line = Keyword.fetch!(meta2, :line) - column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, body]} - pre_func(ast_without_params, state, %{line: line, col: column}, name, params) + pre_func(ast_without_params, state, meta, name, params) end # function head defp pre({def_name, meta, [{name, meta2, params}]}, state) when def_name in @defs and is_atom(name) do - line = Keyword.fetch!(meta2, :line) - column = Keyword.fetch!(meta2, :column) ast_without_params = {def_name, meta, [{name, add_no_call(meta2), []}, nil]} - pre_func(ast_without_params, state, %{line: line, col: column}, name, params) + pre_func(ast_without_params, state, meta, name, params) end defp pre( @@ -609,7 +629,7 @@ defmodule ElixirSense.Core.MetadataBuilder do nil -> # implementation for: Any not detected (is in other file etc.) acc_1 - |> add_module_to_index(mod, {line, column}) + |> add_module_to_index(mod, {line, column}, nil) _any_mods_funs -> # copy implementation for: Any @@ -1146,12 +1166,20 @@ defmodule ElixirSense.Core.MetadataBuilder do state |> new_named_func(name, 1) - |> add_func_to_index(name, [{:\\, [], [{:args, [], nil}, []]}], {line, column}, type, options) + |> add_func_to_index( + name, + [{:\\, [], [{:args, [], nil}, []]}], + {line, column}, + nil, + type, + options + ) |> new_named_func(name, 2) |> add_func_to_index( name, [{:record, [], nil}, {:args, [], nil}], {line, column}, + nil, type, options ) @@ -1262,14 +1290,13 @@ defmodule ElixirSense.Core.MetadataBuilder do post_module(ast, state) end - defp post({def_name, meta, [{name, _, _params}, _]} = ast, state) + defp post({def_name, _meta, [{name, _, _params}, _]} = ast, state) when def_name in @defs and is_atom(name) do - line = Keyword.fetch!(meta, :line) post_func(ast, state) end defp post( - {def_name, meta, [{name, _, _params}, _guards, _]} = ast, + {def_name, _meta, [{name, _, _params}, _guards, _]} = ast, state ) when def_name in @defs and is_atom(name) do @@ -1903,12 +1930,14 @@ defmodule ElixirSense.Core.MetadataBuilder do :exception, [{:msg, [line: line, column: column], nil}], {line, column}, + nil, :def ) |> add_func_to_index( :message, [{:exception, [line: line, column: column], nil}], {line, column}, + nil, :def ) else @@ -1918,16 +1947,18 @@ defmodule ElixirSense.Core.MetadataBuilder do :exception, [{:args, [line: line, column: column], nil}], {line, column}, + nil, :def ) else state end - |> add_func_to_index(:__struct__, [], {line, column}, :def) + |> add_func_to_index(:__struct__, [], {line, column}, nil, :def) |> add_func_to_index( :__struct__, [{:kv, [line: line, column: column], nil}], {line, column}, + nil, :def ) diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index fba72359..9479c638 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -215,6 +215,7 @@ defmodule ElixirSense.Core.State do @type t :: %ModFunInfo{ params: list(list(term)), positions: list(ElixirSense.Core.State.position_t()), + end_positions: list(ElixirSense.Core.State.position_t() | nil), target: nil | {module, atom}, overridable: false | {true, module}, # TODO defmodule defprotocol defimpl? @@ -231,6 +232,7 @@ defmodule ElixirSense.Core.State do defstruct params: [], positions: [], + end_positions: [], target: nil, type: nil, overridable: false @@ -486,6 +488,7 @@ defmodule ElixirSense.Core.State do %__MODULE__{} = state, {module, fun, arity}, position, + end_position, params, type, options \\ [] @@ -494,8 +497,10 @@ defmodule ElixirSense.Core.State do current_info = Map.get(state.mods_funs_to_positions, {module, fun, arity}, %ModFunInfo{}) current_params = current_info |> Map.get(:params, []) current_positions = current_info |> Map.get(:positions, []) + current_end_positions = current_info |> Map.get(:end_positions, []) new_params = [params | current_params] new_positions = [position | current_positions] + new_end_positions = [end_position | current_end_positions] info_type = if fun != nil and arity == nil and @@ -510,6 +515,7 @@ defmodule ElixirSense.Core.State do info = %ModFunInfo{ positions: new_positions, + end_positions: new_end_positions, params: new_params, type: info_type, overridable: current_info |> Map.get(:overridable, false) @@ -687,30 +693,58 @@ defmodule ElixirSense.Core.State do %__MODULE__{state | scopes: tl(state.scopes)} end - def add_current_module_to_index(%__MODULE__{} = state, position) when is_tuple(position) do + def add_current_module_to_index(%__MODULE__{} = state, position, end_position) + when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do current_module_variants = get_current_module_variants(state) current_module_variants |> Enum.reduce(state, fn variant, acc -> acc - |> add_module_to_index(variant, position) + |> add_module_to_index(variant, position, end_position) end) end - def add_module_to_index(%__MODULE__{} = state, module, position) when is_tuple(position) do + def add_module_to_index(%__MODULE__{} = state, module, position, end_position) + when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do # TODO :defprotocol, :defimpl? - add_mod_fun_to_position(state, {module, nil, nil}, position, nil, :defmodule) + add_mod_fun_to_position(state, {module, nil, nil}, position, end_position, nil, :defmodule) end - def add_func_to_index(%__MODULE__{} = state, func, params, position, type, options \\ []) do + # TODO require end position + def add_func_to_index(state, func, params, position, end_position \\ nil, type, options \\ []) + + def add_func_to_index( + %__MODULE__{} = state, + func, + params, + position, + end_position, + type, + options + ) + when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do current_module_variants = get_current_module_variants(state) arity = length(params) current_module_variants |> Enum.reduce(state, fn variant, acc -> acc - |> add_mod_fun_to_position({variant, func, arity}, position, params, type, options) - |> add_mod_fun_to_position({variant, func, nil}, position, params, type, options) + |> add_mod_fun_to_position( + {variant, func, arity}, + position, + end_position, + params, + type, + options + ) + |> add_mod_fun_to_position( + {variant, func, nil}, + position, + end_position, + params, + type, + options + ) end) end diff --git a/test/elixir_sense/core/metadata_builder/alias_test.exs b/test/elixir_sense/core/metadata_builder/alias_test.exs index 0bab37b9..e56ecc63 100644 --- a/test/elixir_sense/core/metadata_builder/alias_test.exs +++ b/test/elixir_sense/core/metadata_builder/alias_test.exs @@ -39,7 +39,7 @@ defmodule ElixirSense.Core.MetadataBuilder.AliasTest do state = unquote(module).module_info()[:compile][:source] |> File.read!() - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() env = unquote(module).env() @@ -57,7 +57,7 @@ defmodule ElixirSense.Core.MetadataBuilder.AliasTest do state = code - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() {env, _} = Code.eval_string(code, []) @@ -75,7 +75,7 @@ defmodule ElixirSense.Core.MetadataBuilder.AliasTest do state = code - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() {env, _} = Code.eval_string(code, []) diff --git a/test/elixir_sense/core/metadata_builder/import_test.exs b/test/elixir_sense/core/metadata_builder/import_test.exs index 86dc1395..7ed8187c 100644 --- a/test/elixir_sense/core/metadata_builder/import_test.exs +++ b/test/elixir_sense/core/metadata_builder/import_test.exs @@ -38,7 +38,7 @@ defmodule ElixirSense.Core.MetadataBuilder.ImportTest do state = unquote(module).module_info()[:compile][:source] |> File.read!() - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() env = unquote(module).env() @@ -64,7 +64,7 @@ defmodule ElixirSense.Core.MetadataBuilder.ImportTest do state = code - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() {env, _} = Code.eval_string(code, []) @@ -84,7 +84,7 @@ defmodule ElixirSense.Core.MetadataBuilder.ImportTest do state = code - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() {env, _} = Code.eval_string(code, []) diff --git a/test/elixir_sense/core/metadata_builder/require_test.exs b/test/elixir_sense/core/metadata_builder/require_test.exs index c3993528..418efce9 100644 --- a/test/elixir_sense/core/metadata_builder/require_test.exs +++ b/test/elixir_sense/core/metadata_builder/require_test.exs @@ -28,7 +28,7 @@ defmodule ElixirSense.Core.MetadataBuilder.RequireTest do state = unquote(module).module_info()[:compile][:source] |> File.read!() - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() env = unquote(module).env() @@ -46,7 +46,7 @@ defmodule ElixirSense.Core.MetadataBuilder.RequireTest do state = code - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() {env, _} = Code.eval_string(code, []) @@ -64,7 +64,7 @@ defmodule ElixirSense.Core.MetadataBuilder.RequireTest do state = code - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) |> MetadataBuilder.build() {env, _} = Code.eval_string(code, []) diff --git a/test/elixir_sense/core/metadata_builder_test.exs b/test/elixir_sense/core/metadata_builder_test.exs index dc35f5e7..eb51dd4e 100644 --- a/test/elixir_sense/core/metadata_builder_test.exs +++ b/test/elixir_sense/core/metadata_builder_test.exs @@ -2852,14 +2852,14 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [["t", "integer"]], kind: :callback, name: :without_spec, - positions: [{6, 7}], + positions: [{6, 3}], specs: ["@callback without_spec(t, integer) :: term"] }, {Proto, :without_spec, 2} => %ElixirSense.Core.State.SpecInfo{ args: [["t", "integer"]], kind: :callback, name: :without_spec, - positions: [{6, 7}], + positions: [{6, 3}], specs: ["@callback without_spec(t, integer) :: term"] } } @@ -2914,12 +2914,12 @@ defmodule ElixirSense.Core.MetadataBuilderTest do }, {OuterModule.InnerModule, :func, 0} => %ModFunInfo{ params: [[]], - positions: [{5, 9}], + positions: [{5, 5}], type: :def }, {OuterModule.InnerModule, :func, nil} => %ModFunInfo{ params: [[]], - positions: [{5, 9}], + positions: [{5, 5}], type: :def }, {Impls, nil, nil} => %ModFunInfo{ @@ -2929,7 +2929,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do }, {Reversible, :reverse, 1} => %ModFunInfo{ params: [[{:term, [line: 19, column: 15], nil}]], - positions: [{19, 7}], + positions: [{19, 3}], type: :def }, {Reversible.String, :__impl__, 1} => %ElixirSense.Core.State.ModFunInfo{ @@ -3169,33 +3169,31 @@ defmodule ElixirSense.Core.MetadataBuilderTest do |> string_to_state assert %{ - mods_funs_to_positions: %{ - {MyModule, :is_even, 1} => %{ - params: [[{:value, [line: 2, column: 20], nil}]], - positions: [{2, 12}] - }, - {MyModule, :is_even, nil} => %{ - params: [[{:value, [line: 2, column: 20], nil}]], - positions: [{2, 12}] - }, - {MyModule, :is_odd, 1} => %{ - params: [[{:value, [line: 3, column: 20], nil}]], - positions: [{3, 13}] - }, - {MyModule, :is_odd, nil} => %{ - params: [[{:value, [line: 3, column: 20], nil}]], - positions: [{3, 13}] - }, - {MyModule, :useless, 0} => %{ - params: [[]], - positions: [{4, 13}] - }, - {MyModule, :useless, nil} => %{ - params: [[]], - positions: [{4, 13}] - } + {MyModule, :is_even, 1} => %{ + params: [[{:value, [line: 2, column: 20], nil}]], + positions: [{2, 3}] + }, + {MyModule, :is_even, nil} => %{ + params: [[{:value, [line: 2, column: 20], nil}]], + positions: [{2, 3}] + }, + {MyModule, :is_odd, 1} => %{ + params: [[{:value, [line: 3, column: 20], nil}]], + positions: [{3, 3}] + }, + {MyModule, :is_odd, nil} => %{ + params: [[{:value, [line: 3, column: 20], nil}]], + positions: [{3, 3}] + }, + {MyModule, :useless, 0} => %{ + params: [[]], + positions: [{4, 3}] + }, + {MyModule, :useless, nil} => %{ + params: [[]], + positions: [{4, 3}] } - } = state + } = state.mods_funs_to_positions end test "registers mods and func" do @@ -3228,155 +3226,125 @@ defmodule ElixirSense.Core.MetadataBuilderTest do assert %{ {MyModuleWithFuns, :func, 0} => %ModFunInfo{ params: [[]], - positions: [{4, 7}], type: :def }, {MyModuleWithFuns, :func, nil} => %ModFunInfo{ params: [[]], - positions: [{4, 7}], type: :def }, {MyModuleWithFuns, :funcp, 0} => %ModFunInfo{ params: [[]], - positions: [{7, 8}], type: :defp }, {MyModuleWithFuns, :funcp, nil} => %ModFunInfo{ params: [[]], - positions: [{7, 8}], type: :defp }, {MyModuleWithFuns, :is_even, 1} => %ModFunInfo{ params: [[{:value, [line: 16, column: 20], nil}]], - positions: [{16, 12}], type: :defguard }, {MyModuleWithFuns, :is_even, nil} => %ModFunInfo{ params: [[{:value, [line: 16, column: 20], nil}]], - positions: [{16, 12}], type: :defguard }, {MyModuleWithFuns, :is_evenp, 1} => %ModFunInfo{ params: [[{:value, [line: 17, column: 22], nil}]], - positions: [{17, 13}], type: :defguardp }, {MyModuleWithFuns, :is_evenp, nil} => %ModFunInfo{ params: [[{:value, [line: 17, column: 22], nil}]], - positions: [{17, 13}], type: :defguardp }, {MyModuleWithFuns, :macro1, 1} => %ModFunInfo{ params: [[{:ast, [line: 10, column: 19], nil}]], - positions: [{10, 12}], type: :defmacro }, {MyModuleWithFuns, :macro1, nil} => %ModFunInfo{ params: [[{:ast, [line: 10, column: 19], nil}]], - positions: [{10, 12}], type: :defmacro }, {MyModuleWithFuns, :macro1p, 1} => %ModFunInfo{ params: [[{:ast, [line: 13, column: 21], nil}]], - positions: [{13, 13}], type: :defmacrop }, {MyModuleWithFuns, :macro1p, nil} => %ModFunInfo{ params: [[{:ast, [line: 13, column: 21], nil}]], - positions: [{13, 13}], type: :defmacrop }, {MyModuleWithFuns, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{3, 1}], type: :defmodule }, {MyModuleWithFuns.Nested, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{19, 3}], type: :defmodule }, {MyModuleWithoutFuns, nil, nil} => %ModFunInfo{ params: [nil], - positions: [{1, 1}], type: :defmodule }, {MyModuleWithFuns, :__info__, 1} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 3, column: 1], nil}]], - positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :__info__, nil} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 3, column: 1], nil}]], - positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :module_info, 0} => %ElixirSense.Core.State.ModFunInfo{ params: [[]], - positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :module_info, 1} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 3, column: 1], nil}]], - positions: [{3, 1}], type: :def }, {MyModuleWithFuns, :module_info, nil} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 3, column: 1], nil}], []], - positions: [{3, 1}, {3, 1}], type: :def }, {MyModuleWithFuns.Nested, :__info__, 1} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 19, column: 3], nil}]], - positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :__info__, nil} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 19, column: 3], nil}]], - positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :module_info, 0} => %ElixirSense.Core.State.ModFunInfo{ params: [[]], - positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :module_info, 1} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 19, column: 3], nil}]], - positions: [{19, 3}], type: :def }, {MyModuleWithFuns.Nested, :module_info, nil} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 19, column: 3], nil}], []], - positions: [{19, 3}, {19, 3}], type: :def }, {MyModuleWithoutFuns, :__info__, 1} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 1, column: 1], nil}]], - positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :__info__, nil} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 1, column: 1], nil}]], - positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :module_info, 0} => %ElixirSense.Core.State.ModFunInfo{ params: [[]], - positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :module_info, 1} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 1, column: 1], nil}]], - positions: [{1, 1}], type: :def }, {MyModuleWithoutFuns, :module_info, nil} => %ElixirSense.Core.State.ModFunInfo{ params: [[{:atom, [line: 1, column: 1], nil}], []], - positions: [{1, 1}, {1, 1}], type: :def } - } == state.mods_funs_to_positions + } = state.mods_funs_to_positions end test "registers delegated func" do @@ -3395,49 +3363,49 @@ defmodule ElixirSense.Core.MetadataBuilderTest do assert %{ {MyModuleWithFuns, :func_delegated, 1} => %ModFunInfo{ params: [[{:par, [line: 3, column: 30], nil}]], - positions: [{3, 15}], + positions: [{3, 3}], target: {OtherModule, :func_delegated}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated, nil} => %ModFunInfo{ params: [[{:par, [line: 3, column: 30], nil}]], - positions: [{3, 15}], + positions: [{3, 3}], target: {OtherModule, :func_delegated}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated_alias, 1} => %ModFunInfo{ params: [[{:par, [line: 6, column: 36], nil}]], - positions: [{6, 15}], + positions: [{6, 3}], target: {Enum, :func_delegated_alias}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated_alias, nil} => %ModFunInfo{ params: [[{:par, [line: 6, column: 36], nil}]], - positions: [{6, 15}], + positions: [{6, 3}], target: {Enum, :func_delegated_alias}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated_as, 1} => %ModFunInfo{ params: [[{:par, [line: 5, column: 33], nil}]], - positions: [{5, 15}], + positions: [{5, 3}], target: {MyModuleWithFuns.Sub, :my_func}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated_as, nil} => %ModFunInfo{ params: [[{:par, [line: 5, column: 33], nil}]], - positions: [{5, 15}], + positions: [{5, 3}], target: {MyModuleWithFuns.Sub, :my_func}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated_erlang, 1} => %ModFunInfo{ params: [[{:par, [line: 4, column: 37], nil}]], - positions: [{4, 15}], + positions: [{4, 3}], target: {:erlang_module, :func_delegated_erlang}, type: :defdelegate }, {MyModuleWithFuns, :func_delegated_erlang, nil} => %ModFunInfo{ params: [[{:par, [line: 4, column: 37], nil}]], - positions: [{4, 15}], + positions: [{4, 3}], target: {:erlang_module, :func_delegated_erlang}, type: :defdelegate } @@ -4867,7 +4835,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do [{:baz, [line: 8, column: 21], nil}], [{:var, [line: 2, column: 3], nil}] ], - positions: [{8, 12}, {2, 3}], + positions: [{8, 3}, {2, 3}], target: nil, type: :defmacro, overridable: {true, ElixirSenseExample.OverridableFunctions} @@ -4877,7 +4845,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do [{:a, [line: 4, column: 12], nil}, {:b, [line: 4, column: 15], nil}], [{:x, [line: 2, column: 3], nil}, {:y, [line: 2, column: 3], nil}] ], - positions: [{4, 7}, {2, 3}], + positions: [{4, 3}, {2, 3}], target: nil, type: :def, overridable: {true, ElixirSenseExample.OverridableFunctions} @@ -4903,7 +4871,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do assert %{ {My, :foo, 0} => %ModFunInfo{ params: [[], []], - positions: [{4, 7}, {2, 3}], + positions: [{4, 3}, {2, 3}], target: nil, type: :def, overridable: {true, ElixirSenseExample.OverridableImplementation} @@ -4913,7 +4881,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do [{:baz, [line: 8, column: 16], nil}], [{:var, [line: 2, column: 3], nil}] ], - positions: [{8, 12}, {2, 3}], + positions: [{8, 3}, {2, 3}], target: nil, type: :defmacro, overridable: {true, ElixirSenseExample.OverridableImplementation} @@ -4942,7 +4910,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do [{:baz, [line: 8, column: 22], nil}], [{:var, [line: 2, column: 3], nil}] ], - positions: [{8, 13}, {2, 3}], + positions: [{8, 3}, {2, 3}], target: nil, type: :defmacrop, overridable: {true, ElixirSenseExample.OverridableFunctions} @@ -4955,7 +4923,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do [{:a, [line: 4, column: 13], nil}, {:b, [line: 4, column: 16], nil}], [{:x, [line: 2, column: 3], nil}, {:y, [line: 2, column: 3], nil}] ], - positions: [{4, 8}, {2, 3}], + positions: [{4, 3}, {2, 3}], target: nil, type: :defp, overridable: {true, ElixirSenseExample.OverridableFunctions} @@ -5091,7 +5059,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do {:ok, ast} = File.read!(file) - |> Code.string_to_quoted(columns: true) + |> Code.string_to_quoted(columns: true, token_metadata: true) acc = MetadataBuilder.build(ast) @@ -5117,7 +5085,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do for path <- elixir_sense_src_path ++ elixir_src_path do case File.read!(path) - |> Code.string_to_quoted(columns: true) do + |> Code.string_to_quoted(columns: true, token_metadata: true) do {:ok, ast} -> MetadataBuilder.build(ast) _ -> :ok end diff --git a/test/elixir_sense/definition_test.exs b/test/elixir_sense/definition_test.exs index c4ff9bcd..8bbe0a86 100644 --- a/test/elixir_sense/definition_test.exs +++ b/test/elixir_sense/definition_test.exs @@ -167,7 +167,7 @@ defmodule ElixirSense.Providers.DefinitionTest do end """ - assert %Location{type: :function, file: nil, line: 5, column: 7} = + assert %Location{type: :function, file: nil, line: 5, column: 3} = ElixirSense.definition(buffer, 2, 18) end @@ -179,7 +179,7 @@ defmodule ElixirSense.Providers.DefinitionTest do end """ - assert %Location{type: :function, file: nil, line: 3, column: 7} = + assert %Location{type: :function, file: nil, line: 3, column: 3} = ElixirSense.definition(buffer, 3, 9) end @@ -454,7 +454,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 2, - column: 7 + column: 3 } end @@ -474,7 +474,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 2, - column: 7 + column: 3 } end @@ -771,7 +771,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 2, - column: 7 + column: 3 } end @@ -791,7 +791,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 2, - column: 7 + column: 3 } end @@ -812,7 +812,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 2, - column: 7 + column: 3 } end @@ -832,7 +832,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 2, - column: 7 + column: 3 } end @@ -851,7 +851,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :macro, file: nil, line: 2, - column: 13 + column: 3 } end @@ -873,7 +873,7 @@ defmodule ElixirSense.Providers.DefinitionTest do type: :function, file: nil, line: 3, - column: 7 + column: 3 } end From 868801192afb3f46587d19f4332230d254c9cd5b Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Tue, 13 Jun 2023 08:37:13 +0200 Subject: [PATCH 07/16] wip --- lib/elixir_sense.ex | 4 +-- lib/elixir_sense/core/metadata.ex | 32 +++++++++++-------- lib/elixir_sense/core/metadata_builder.ex | 1 + lib/elixir_sense/core/state.ex | 4 +++ .../providers/suggestion/reducers/common.ex | 7 ++-- .../suggestion/reducers/docs_snippets.ex | 3 +- .../suggestion/reducers/type_specs.ex | 2 +- test/elixir_sense/core/source_test.exs | 4 +-- test/elixir_sense/suggestions_test.exs | 8 +---- 9 files changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/elixir_sense.ex b/lib/elixir_sense.ex index e2fb8c45..d4fa6a0e 100644 --- a/lib/elixir_sense.ex +++ b/lib/elixir_sense.ex @@ -225,13 +225,13 @@ defmodule ElixirSense do buffer_file_metadata = maybe_fix_autocomple_on_cursor(buffer_file_metadata, text_before, text_after, line) - env = Metadata.get_env(buffer_file_metadata, line) + env = Metadata.get_env(buffer_file_metadata, {line, column}) module_store = ModuleStore.build() cursor_context = %{ text_before: text_before, text_after: text_after, - at_module_body?: Metadata.at_module_body?(buffer_file_metadata, env) + at_module_body?: Metadata.at_module_body?(buffer_file_metadata, env, {line, column}) } Suggestion.find(hint, env, buffer_file_metadata, cursor_context, module_store, opts) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 94c2ef54..780793ac 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -41,25 +41,31 @@ defmodule ElixirSense.Core.Metadata do documentation: String.t() } - @spec get_env(__MODULE__.t(), pos_integer) :: State.Env.t() - def get_env(%__MODULE__{} = metadata, line) do - case Map.get(metadata.lines_to_env, line) do + @spec get_env(__MODULE__.t(), {pos_integer, pos_integer}) :: State.Env.t() + def get_env(%__MODULE__{} = metadata, {line, column}) do + case Map.get(metadata.lines_to_env, line) |> dbg do nil -> State.default_env() ctx -> ctx end end - @spec at_module_body?(__MODULE__.t(), State.Env.t()) :: boolean() - def at_module_body?(%__MODULE__{} = metadata, env) do - mod_info = Map.get(metadata.mods_funs_to_positions, {env.module, nil, nil}) + @spec at_module_body?(__MODULE__.t(), State.Env.t(), {pos_integer, pos_integer}) :: boolean() + def at_module_body?(%__MODULE__{} = metadata, env, {line, column}) do + modules = metadata.mods_funs_to_positions + |> Enum.filter(&match?({{module, nil, nil}, _} when is_atom(module), &1)) + + found_module_with_end_position = modules + |> Enum.any?(fn {_, %State.ModFunInfo{positions: positions, end_positions: end_positions}} -> + Enum.zip(positions, end_positions) + |> Enum.any?(fn + {{begin_line, begin_column}, {end_line, end_column}} -> + (line > begin_line or line == begin_line and column > begin_column) and + (line < end_line or line == end_line and column < end_column) + {_, nil} -> false + end) + end) - with %State.ModFunInfo{positions: [{line, _}]} <- mod_info, - %State.Env{scope_id: mod_scope_id} <- metadata.lines_to_env[line] do - env.scope_id in mod_scope_id..(mod_scope_id + 1) and not match?({_, _}, env.scope) - else - _ -> - false - end + found_module_with_end_position and match?(atom when is_atom(atom), env.scope) end def get_position_to_insert_alias(%__MODULE__{} = metadata, line) do diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index dc2a0ce0..8914013b 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -54,6 +54,7 @@ defmodule ElixirSense.Core.MetadataBuilder do """ @spec build(Macro.t()) :: State.t() def build(ast) do + dbg(ast) {_ast, state} = Macro.traverse(ast, %State{}, safe_call(&pre/2, :pre), safe_call(&post/2, :post)) diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index 9479c638..fe8256fe 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -308,6 +308,9 @@ defmodule ElixirSense.Core.State do end def add_current_env_to_line(%__MODULE__{} = state, line) when is_integer(line) do + if line == 6 do + dbg + end previous_env = state.lines_to_env[line] current_env = get_current_env(state) @@ -1312,6 +1315,7 @@ defmodule ElixirSense.Core.State do defp merge_type(old, new), do: {:intersection, [old, new]} def get_closest_previous_env(%__MODULE__{} = metadata, line) do + dbg() metadata.lines_to_env |> Enum.max_by( fn diff --git a/lib/elixir_sense/providers/suggestion/reducers/common.ex b/lib/elixir_sense/providers/suggestion/reducers/common.ex index 91d8081b..86267224 100644 --- a/lib/elixir_sense/providers/suggestion/reducers/common.ex +++ b/lib/elixir_sense/providers/suggestion/reducers/common.ex @@ -64,7 +64,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do def populate(hint, env, buffer_metadata, context, acc, opts \\ []) do text_before = context.text_before - suggestions = find_mods_funcs(hint, env, buffer_metadata, text_before, opts) + suggestions = find_mods_funcs(hint, env, buffer_metadata, text_before, opts) |> dbg suggestions_by_type = Enum.group_by(suggestions, & &1.type) @@ -113,6 +113,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do Note: requires populate/5. """ def add_attributes(_hint, _env, _file_metadata, _context, acc) do + IO.puts "adding attrs" add_suggestions(:attribute, acc) end @@ -127,7 +128,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do defp add_suggestions(type, acc) do suggestions_by_type = Reducer.get_context(acc, :common_suggestions_by_type) - list = Map.get(suggestions_by_type, type, []) + list = Map.get(suggestions_by_type, type, []) |> dbg {:cont, %{acc | result: acc.result ++ list}} end @@ -185,7 +186,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do else hint end - + dbg({hint, env, opts}) Complete.complete(hint, env, opts) end end diff --git a/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex b/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex index 39dd25d1..937e24a7 100644 --- a/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex +++ b/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex @@ -18,7 +18,8 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.DocsSnippets do @doc """ A reducer that adds suggestions for @doc, @moduledoc and @typedoc. """ - def add_snippets(hint, _env, _metadata, %{at_module_body?: true}, acc) do + def add_snippets(hint, env, _metadata, %{at_module_body?: true}, acc) do + IO.inspect env.scope list = for {label, snippet, doc, priority} <- @module_attr_snippets, Matcher.match?(label, hint) do diff --git a/lib/elixir_sense/providers/suggestion/reducers/type_specs.ex b/lib/elixir_sense/providers/suggestion/reducers/type_specs.ex index 7361ab7d..4a6c44c9 100644 --- a/lib/elixir_sense/providers/suggestion/reducers/type_specs.ex +++ b/lib/elixir_sense/providers/suggestion/reducers/type_specs.ex @@ -26,7 +26,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.TypeSpecs do """ # We only list type specs when inside typespec scope - def add_types(hint, env, file_metadata, %{at_module_body?: true}, acc) do + def add_types(hint, env, file_metadata, %{at_module_body?: _}, acc) do if match?({:typespec, _, _}, env.scope) do %State.Env{ aliases: aliases, diff --git a/test/elixir_sense/core/source_test.exs b/test/elixir_sense/core/source_test.exs index af4b6e5a..69e76690 100644 --- a/test/elixir_sense/core/source_test.exs +++ b/test/elixir_sense/core/source_test.exs @@ -586,9 +586,9 @@ defmodule ElixirSense.Core.SourceTest do code = "from u in " if Version.match?(System.version(), ">= 1.15.0-dev") do - %{candidate: {nil, :in}, npar: 1} = which_func(code) + assert %{candidate: {nil, :in}, npar: 1} = which_func(code) else - nil == which_func(code) + assert nil == which_func(code) end end diff --git a/test/elixir_sense/suggestions_test.exs b/test/elixir_sense/suggestions_test.exs index bc2d2e93..2614020b 100644 --- a/test/elixir_sense/suggestions_test.exs +++ b/test/elixir_sense/suggestions_test.exs @@ -1613,15 +1613,10 @@ defmodule ElixirSense.SuggestionsTest do @m # ^ end - - schema do - @m - # ^ - end end """ - [cursor_1, cursor_2, cursor_3, cursor_4] = cursors(buffer) + [cursor_1, cursor_2, cursor_3] = cursors(buffer) list = suggestions_by_kind(buffer, cursor_1, :snippet) @@ -1641,7 +1636,6 @@ defmodule ElixirSense.SuggestionsTest do assert [%{label: ~S(@moduledoc """""")}, %{label: "@moduledoc false"}] = list assert suggestions_by_kind(buffer, cursor_3, :snippet) == [] - assert suggestions_by_kind(buffer, cursor_4, :snippet) == [] end test "fuzzy suggestions for doc snippets" do From 9d3434087544e7466435a33aaeee25df95014285 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Mon, 3 Jul 2023 22:30:37 +0200 Subject: [PATCH 08/16] make test pass --- lib/elixir_sense.ex | 12 ++--- lib/elixir_sense/core/metadata.ex | 50 +++++++++++++++++-- lib/elixir_sense/core/metadata_builder.ex | 1 - lib/elixir_sense/core/state.ex | 4 -- .../providers/suggestion/reducers/common.ex | 6 +-- test/elixir_sense/suggestions_test.exs | 15 +----- 6 files changed, 57 insertions(+), 31 deletions(-) diff --git a/lib/elixir_sense.ex b/lib/elixir_sense.ex index d4fa6a0e..6aafb761 100644 --- a/lib/elixir_sense.ex +++ b/lib/elixir_sense.ex @@ -69,7 +69,7 @@ defmodule ElixirSense do %{begin: begin_pos, end: end_pos, context: context} -> metadata = Parser.parse_string(code, true, true, line) - env = Metadata.get_env(metadata, line) + env = Metadata.get_env(metadata, {line, column}) case Docs.all(context, env, metadata.mods_funs_to_positions, metadata.types) do {actual_subject, docs} -> @@ -112,7 +112,7 @@ defmodule ElixirSense do %{context: context, begin: {line, col}} -> buffer_file_metadata = Parser.parse_string(code, true, true, line) - env = Metadata.get_env(buffer_file_metadata, line) + env = Metadata.get_env(buffer_file_metadata, {line, column}) calls = buffer_file_metadata.calls[line] @@ -154,7 +154,7 @@ defmodule ElixirSense do %{context: context} -> buffer_file_metadata = Parser.parse_string(code, true, true, line) - env = Metadata.get_env(buffer_file_metadata, line) + env = Metadata.get_env(buffer_file_metadata, {line, column}) Implementation.find( context, @@ -267,7 +267,7 @@ defmodule ElixirSense do prefix = Source.text_before(code, line, column) buffer_file_metadata = Parser.parse_string(code, true, true, line) - env = Metadata.get_env(buffer_file_metadata, line) + env = Metadata.get_env(buffer_file_metadata, {line, column}) Signature.find(prefix, env, buffer_file_metadata) end @@ -350,7 +350,7 @@ defmodule ElixirSense do def expand_full(buffer, code, line) do buffer_file_metadata = Parser.parse_string(buffer, true, true, line) - env = Metadata.get_env(buffer_file_metadata, line) + env = Metadata.get_env(buffer_file_metadata, {line, 1}) Expand.expand_full(code, env) end @@ -432,7 +432,7 @@ defmodule ElixirSense do %State.Env{ module: module, vars: vars - } = Metadata.get_env(buffer_file_metadata, line) + } = Metadata.get_env(buffer_file_metadata, {line, column}) # find last env of current module attributes = get_attributes(buffer_file_metadata.lines_to_env, module) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 780793ac..cfc8c54b 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -43,10 +43,54 @@ defmodule ElixirSense.Core.Metadata do @spec get_env(__MODULE__.t(), {pos_integer, pos_integer}) :: State.Env.t() def get_env(%__MODULE__{} = metadata, {line, column}) do - case Map.get(metadata.lines_to_env, line) |> dbg do - nil -> State.default_env() - ctx -> ctx + # TODO this should handle spec scope + closest_scope = metadata.mods_funs_to_positions + |> Enum.map(fn + {{_, fun, nil}, _} when fun != nil -> nil + {key, %State.ModFunInfo{positions: positions, end_positions: end_positions}} -> + closest_scope = Enum.zip(positions, end_positions) + |> Enum.map(fn + {{begin_line, begin_column}, {end_line, end_column}} when + ((line > begin_line or line == begin_line and column > begin_column) and + (line < end_line or line == end_line and column < end_column)) -> + {begin_line, begin_column} + + _ -> nil + end) + |> Enum.filter(& &1 != nil) + |> Enum.max(fn -> nil end) + + if closest_scope do + {key, closest_scope} + end + end) + |> Enum.filter(& &1 != nil) + |> Enum.max_by(fn {_key, begin_position} -> + begin_position + end, fn -> nil end) + + case closest_scope do + {key, {begin_line, _begin_column}} -> + metadata.lines_to_env + |> Enum.filter(fn + {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> + case key do + {module, nil, nil} -> + env.module == module and is_atom(env.scope) + {module, fun, arity} -> + env.module == module and env.scope == {fun, arity} + end + _ -> false + end) + nil -> metadata.lines_to_env end + |> Enum.max_by(fn {metadata_line, _env} -> metadata_line end, fn -> {line, State.default_env()} end) + |> elem(1) + + # case Map.get(metadata.lines_to_env, line) |> dbg do + # nil -> State.default_env() + # ctx -> ctx + # end end @spec at_module_body?(__MODULE__.t(), State.Env.t(), {pos_integer, pos_integer}) :: boolean() diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index 8914013b..dc2a0ce0 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -54,7 +54,6 @@ defmodule ElixirSense.Core.MetadataBuilder do """ @spec build(Macro.t()) :: State.t() def build(ast) do - dbg(ast) {_ast, state} = Macro.traverse(ast, %State{}, safe_call(&pre/2, :pre), safe_call(&post/2, :post)) diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index fe8256fe..9479c638 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -308,9 +308,6 @@ defmodule ElixirSense.Core.State do end def add_current_env_to_line(%__MODULE__{} = state, line) when is_integer(line) do - if line == 6 do - dbg - end previous_env = state.lines_to_env[line] current_env = get_current_env(state) @@ -1315,7 +1312,6 @@ defmodule ElixirSense.Core.State do defp merge_type(old, new), do: {:intersection, [old, new]} def get_closest_previous_env(%__MODULE__{} = metadata, line) do - dbg() metadata.lines_to_env |> Enum.max_by( fn diff --git a/lib/elixir_sense/providers/suggestion/reducers/common.ex b/lib/elixir_sense/providers/suggestion/reducers/common.ex index 86267224..499db98f 100644 --- a/lib/elixir_sense/providers/suggestion/reducers/common.ex +++ b/lib/elixir_sense/providers/suggestion/reducers/common.ex @@ -64,7 +64,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do def populate(hint, env, buffer_metadata, context, acc, opts \\ []) do text_before = context.text_before - suggestions = find_mods_funcs(hint, env, buffer_metadata, text_before, opts) |> dbg + suggestions = find_mods_funcs(hint, env, buffer_metadata, text_before, opts) suggestions_by_type = Enum.group_by(suggestions, & &1.type) @@ -113,7 +113,6 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do Note: requires populate/5. """ def add_attributes(_hint, _env, _file_metadata, _context, acc) do - IO.puts "adding attrs" add_suggestions(:attribute, acc) end @@ -128,7 +127,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do defp add_suggestions(type, acc) do suggestions_by_type = Reducer.get_context(acc, :common_suggestions_by_type) - list = Map.get(suggestions_by_type, type, []) |> dbg + list = Map.get(suggestions_by_type, type, []) {:cont, %{acc | result: acc.result ++ list}} end @@ -186,7 +185,6 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do else hint end - dbg({hint, env, opts}) Complete.complete(hint, env, opts) end end diff --git a/test/elixir_sense/suggestions_test.exs b/test/elixir_sense/suggestions_test.exs index 2614020b..26a9b147 100644 --- a/test/elixir_sense/suggestions_test.exs +++ b/test/elixir_sense/suggestions_test.exs @@ -1585,19 +1585,8 @@ defmodule ElixirSense.SuggestionsTest do ElixirSense.suggestions(buffer, 6, 4) |> Enum.filter(fn s -> s.type == :attribute end) - assert list == [ - %{name: "@macrocallback", type: :attribute}, - %{name: "@moduledoc", type: :attribute}, - %{name: "@myattr", type: :attribute} - ] - - list = - ElixirSense.suggestions(buffer, 5, 7) - |> Enum.filter(fn s -> s.type == :attribute end) - - assert list == [ - %{name: "@myattr", type: :attribute} - ] + assert Enum.any?(list, & &1.name == "@impl") + assert Enum.any?(list, & &1.name == "@spec") end test "lists doc snippets in module body" do From fdcae2882417c763c0ccf5e78799938ae7850557 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Tue, 4 Jul 2023 08:14:06 +0200 Subject: [PATCH 09/16] skip generated code when finding scopes --- lib/elixir_sense/core/metadata.ex | 23 ++++++----- lib/elixir_sense/core/metadata_builder.ex | 39 +++++++++++++------ lib/elixir_sense/core/state.ex | 19 ++++++--- .../suggestion/reducers/docs_snippets.ex | 1 - 4 files changed, 51 insertions(+), 31 deletions(-) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index cfc8c54b..86a4765d 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -43,18 +43,20 @@ defmodule ElixirSense.Core.Metadata do @spec get_env(__MODULE__.t(), {pos_integer, pos_integer}) :: State.Env.t() def get_env(%__MODULE__{} = metadata, {line, column}) do - # TODO this should handle spec scope closest_scope = metadata.mods_funs_to_positions |> Enum.map(fn {{_, fun, nil}, _} when fun != nil -> nil - {key, %State.ModFunInfo{positions: positions, end_positions: end_positions}} -> - closest_scope = Enum.zip(positions, end_positions) + {key, %State.ModFunInfo{positions: positions, end_positions: end_positions, generated: generated}} -> + closest_scope = Enum.zip([positions, end_positions, generated]) |> Enum.map(fn - {{begin_line, begin_column}, {end_line, end_column}} when + {_, _, true} -> nil + {{begin_line, begin_column}, {end_line, end_column}, _} when ((line > begin_line or line == begin_line and column > begin_column) and (line < end_line or line == end_line and column < end_column)) -> {begin_line, begin_column} - + {{begin_line, begin_column}, nil, _} when + (line > begin_line or line == begin_line and column > begin_column) -> + {begin_line, begin_column} _ -> nil end) |> Enum.filter(& &1 != nil) @@ -68,6 +70,7 @@ defmodule ElixirSense.Core.Metadata do |> Enum.max_by(fn {_key, begin_position} -> begin_position end, fn -> nil end) + # |> dbg() case closest_scope do {key, {begin_line, _begin_column}} -> @@ -76,21 +79,17 @@ defmodule ElixirSense.Core.Metadata do {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> case key do {module, nil, nil} -> - env.module == module and is_atom(env.scope) + module in env.module_variants and (is_atom(env.scope) or match?({:typespec, _, _}, env.scope)) {module, fun, arity} -> - env.module == module and env.scope == {fun, arity} + module in env.module_variants and env.scope == {fun, arity} end _ -> false end) + # |> dbg() nil -> metadata.lines_to_env end |> Enum.max_by(fn {metadata_line, _env} -> metadata_line end, fn -> {line, State.default_env()} end) |> elem(1) - - # case Map.get(metadata.lines_to_env, line) |> dbg do - # nil -> State.default_env() - # ctx -> ctx - # end end @spec at_module_body?(__MODULE__.t(), State.Env.t(), {pos_integer, pos_integer}) :: boolean() diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index dc2a0ce0..e6138919 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -54,6 +54,7 @@ defmodule ElixirSense.Core.MetadataBuilder do """ @spec build(Macro.t()) :: State.t() def build(ast) do + # dbg(ast) {_ast, state} = Macro.traverse(ast, %State{}, safe_call(&pre/2, :pre), safe_call(&post/2, :post)) @@ -93,7 +94,7 @@ defmodule ElixirSense.Core.MetadataBuilder do state |> maybe_add_protocol_implementation(module) |> add_namespace(module) - |> add_current_module_to_index(position, end_position) + |> add_current_module_to_index(position, end_position, [generated: state.generated]) |> alias_submodule(module) |> new_alias_scope |> new_attributes_scope @@ -107,7 +108,7 @@ defmodule ElixirSense.Core.MetadataBuilder do types |> Enum.reduce(state, fn {type_name, type_args, spec, kind}, acc -> acc - |> add_type(type_name, type_args, kind, spec, position) + |> add_type(type_name, type_args, kind, spec, position, generated: true) end) state = @@ -121,7 +122,8 @@ defmodule ElixirSense.Core.MetadataBuilder do mapped_args, position, nil, - kind + kind, + [generated: true] ) end) @@ -231,6 +233,8 @@ defmodule ElixirSense.Core.MetadataBuilder do {position, end_position} = extract_range(meta) + options = Keyword.put(options, :generated, state.generated) + state |> new_named_func(name, length(params || [])) |> add_func_to_index(name, params || [], position, end_position, type, options) @@ -454,7 +458,8 @@ defmodule ElixirSense.Core.MetadataBuilder do [{:atom, [line: line, column: column], nil}], pos, nil, - :def + :def, + generated: true ) else state @@ -629,7 +634,7 @@ defmodule ElixirSense.Core.MetadataBuilder do nil -> # implementation for: Any not detected (is in other file etc.) acc_1 - |> add_module_to_index(mod, {line, column}, nil) + |> add_module_to_index(mod, {line, column}, nil, generated: true) _any_mods_funs -> # copy implementation for: Any @@ -1046,7 +1051,7 @@ defmodule ElixirSense.Core.MetadataBuilder do meta |> Keyword.take([:line, :column]) ) - {expanded_ast, state} + {{:__generated__, [], [expanded_ast]}, %{state | generated: true}} end defp pre({type, meta, fields} = ast, state) @@ -1162,7 +1167,7 @@ defmodule ElixirSense.Core.MetadataBuilder do :defrecordp -> :defmacrop end - options = [] + options = [generated: true] state |> new_named_func(name, 1) @@ -1384,6 +1389,10 @@ defmodule ElixirSense.Core.MetadataBuilder do post_clause(ast, state) end + defp post({:__generated__, _meta, inner}, state) do + {inner, %{state | generated: false}} + end + defp post({atom, meta, [lhs, rhs]} = ast, state) when atom in [:=, :<-] do line = Keyword.fetch!(meta, :line) @@ -1918,6 +1927,8 @@ defmodule ElixirSense.Core.MetadataBuilder do [] end + options = [generated: true] + state = if type == :defexception do state = @@ -1931,14 +1942,16 @@ defmodule ElixirSense.Core.MetadataBuilder do [{:msg, [line: line, column: column], nil}], {line, column}, nil, - :def + :def, + options ) |> add_func_to_index( :message, [{:exception, [line: line, column: column], nil}], {line, column}, nil, - :def + :def, + options ) else state @@ -1948,18 +1961,20 @@ defmodule ElixirSense.Core.MetadataBuilder do [{:args, [line: line, column: column], nil}], {line, column}, nil, - :def + :def, + options ) else state end - |> add_func_to_index(:__struct__, [], {line, column}, nil, :def) + |> add_func_to_index(:__struct__, [], {line, column}, nil, :def, options) |> add_func_to_index( :__struct__, [{:kv, [line: line, column: column], nil}], {line, column}, nil, - :def + :def, + options ) state diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index 9479c638..a71042af 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -52,6 +52,7 @@ defmodule ElixirSense.Core.State do calls: calls_t, structs: structs_t, types: types_t, + generated: boolean, first_alias_positions: map(), moduledoc_positions: map(), # TODO @@ -81,6 +82,7 @@ defmodule ElixirSense.Core.State do calls: %{}, structs: %{}, types: %{}, + generated: false, binding_context: [], first_alias_positions: %{}, moduledoc_positions: %{} @@ -148,9 +150,10 @@ defmodule ElixirSense.Core.State do args: list(list(String.t())), specs: [String.t()], kind: :type | :typep | :opaque, + generated: boolean, positions: [ElixirSense.Core.State.position_t()] } - defstruct name: nil, args: [], specs: [], kind: :type, positions: [] + defstruct name: nil, args: [], specs: [], kind: :type, positions: [], generated: false end defmodule SpecInfo do @@ -218,6 +221,7 @@ defmodule ElixirSense.Core.State do end_positions: list(ElixirSense.Core.State.position_t() | nil), target: nil | {module, atom}, overridable: false | {true, module}, + generated: list(boolean), # TODO defmodule defprotocol defimpl? type: :def @@ -235,6 +239,7 @@ defmodule ElixirSense.Core.State do end_positions: [], target: nil, type: nil, + generated: [], overridable: false def get_arities(%ModFunInfo{params: params_variants}) do @@ -518,6 +523,7 @@ defmodule ElixirSense.Core.State do end_positions: new_end_positions, params: new_params, type: info_type, + generated: [Keyword.get(options, :generated, false) | current_info.generated], overridable: current_info |> Map.get(:overridable, false) } @@ -693,21 +699,21 @@ defmodule ElixirSense.Core.State do %__MODULE__{state | scopes: tl(state.scopes)} end - def add_current_module_to_index(%__MODULE__{} = state, position, end_position) + def add_current_module_to_index(%__MODULE__{} = state, position, end_position, options) when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do current_module_variants = get_current_module_variants(state) current_module_variants |> Enum.reduce(state, fn variant, acc -> acc - |> add_module_to_index(variant, position, end_position) + |> add_module_to_index(variant, position, end_position, options) end) end - def add_module_to_index(%__MODULE__{} = state, module, position, end_position) + def add_module_to_index(%__MODULE__{} = state, module, position, end_position, options) when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do # TODO :defprotocol, :defimpl? - add_mod_fun_to_position(state, {module, nil, nil}, position, end_position, nil, :defmodule) + add_mod_fun_to_position(state, {module, nil, nil}, position, end_position, nil, :defmodule, options) end # TODO require end position @@ -1036,7 +1042,7 @@ defmodule ElixirSense.Core.State do Enum.reduce(modules, state, fn mod, state -> add_require(state, mod) end) end - def add_type(%__MODULE__{} = state, type_name, type_args, spec, kind, pos) do + def add_type(%__MODULE__{} = state, type_name, type_args, spec, kind, pos, options \\ []) do arg_names = type_args |> Enum.map(&Macro.to_string/1) @@ -1046,6 +1052,7 @@ defmodule ElixirSense.Core.State do args: [arg_names], kind: kind, specs: [spec], + generated: Keyword.get(options, :generated, false), positions: [pos] } diff --git a/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex b/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex index 937e24a7..b626f24f 100644 --- a/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex +++ b/lib/elixir_sense/providers/suggestion/reducers/docs_snippets.ex @@ -19,7 +19,6 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.DocsSnippets do A reducer that adds suggestions for @doc, @moduledoc and @typedoc. """ def add_snippets(hint, env, _metadata, %{at_module_body?: true}, acc) do - IO.inspect env.scope list = for {label, snippet, doc, priority} <- @module_attr_snippets, Matcher.match?(label, hint) do From 2912add63101de6de1885a835b11308a29e7d171 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Tue, 4 Jul 2023 21:55:10 +0200 Subject: [PATCH 10/16] better handling for types --- lib/elixir_sense/core/metadata.ex | 24 +++++++++++------- lib/elixir_sense/core/metadata_builder.ex | 4 +-- lib/elixir_sense/core/state.ex | 30 ++++++++++++++++------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 86a4765d..77f34465 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -43,10 +43,14 @@ defmodule ElixirSense.Core.Metadata do @spec get_env(__MODULE__.t(), {pos_integer, pos_integer}) :: State.Env.t() def get_env(%__MODULE__{} = metadata, {line, column}) do - closest_scope = metadata.mods_funs_to_positions + all_scopes = Enum.to_list(metadata.types) ++ + Enum.to_list(metadata.specs) ++ + Enum.to_list(metadata.mods_funs_to_positions) + + closest_scope = all_scopes |> Enum.map(fn {{_, fun, nil}, _} when fun != nil -> nil - {key, %State.ModFunInfo{positions: positions, end_positions: end_positions, generated: generated}} -> + {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> closest_scope = Enum.zip([positions, end_positions, generated]) |> Enum.map(fn {_, _, true} -> nil @@ -63,25 +67,27 @@ defmodule ElixirSense.Core.Metadata do |> Enum.max(fn -> nil end) if closest_scope do - {key, closest_scope} + {key, type, closest_scope} end end) |> Enum.filter(& &1 != nil) - |> Enum.max_by(fn {_key, begin_position} -> + |> Enum.max_by(fn {_key, _type, begin_position} -> begin_position end, fn -> nil end) # |> dbg() case closest_scope do - {key, {begin_line, _begin_column}} -> + {key, type, {begin_line, _begin_column}} -> metadata.lines_to_env |> Enum.filter(fn {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> - case key do - {module, nil, nil} -> - module in env.module_variants and (is_atom(env.scope) or match?({:typespec, _, _}, env.scope)) - {module, fun, arity} -> + case {key, type} do + {{module, nil, nil}, _} -> + module in env.module_variants and (is_atom(env.scope)) + {{module, fun, arity}, State.ModFunInfo} -> module in env.module_variants and env.scope == {fun, arity} + {{module, fun, arity}, type} when type in [State.TypeInfo, State.SpecInfo] -> + module in env.module_variants and env.scope == {:typespec, fun, arity} end _ -> false end) diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index e6138919..b8da9bab 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -441,7 +441,7 @@ defmodule ElixirSense.Core.MetadataBuilder do spec = TypeInfo.typespec_to_string(kind, spec) state - |> add_type(type_name, type_args, spec, kind, pos) + |> add_type(type_name, type_args, spec, kind, pos, generated: state.generated) |> add_typespec_namespace(type_name, length(type_args)) |> add_current_env_to_line(line) |> result(ast) @@ -466,7 +466,7 @@ defmodule ElixirSense.Core.MetadataBuilder do end state - |> add_spec(type_name, type_args, spec, kind, pos) + |> add_spec(type_name, type_args, spec, kind, pos, generated: state.generated) |> add_typespec_namespace(type_name, length(type_args)) |> add_current_env_to_line(line) |> result(ast) diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index a71042af..8877d50d 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -150,10 +150,11 @@ defmodule ElixirSense.Core.State do args: list(list(String.t())), specs: [String.t()], kind: :type | :typep | :opaque, - generated: boolean, - positions: [ElixirSense.Core.State.position_t()] + positions: [ElixirSense.Core.State.position_t()], + end_positions: [ElixirSense.Core.State.position_t() | nil], + generated: list(boolean) } - defstruct name: nil, args: [], specs: [], kind: :type, positions: [], generated: false + defstruct name: nil, args: [], specs: [], kind: :type, positions: [], end_positions: [], generated: [] end defmodule SpecInfo do @@ -165,9 +166,11 @@ defmodule ElixirSense.Core.State do args: list(list(String.t())), specs: [String.t()], kind: :spec | :callback | :macrocallback, - positions: [ElixirSense.Core.State.position_t()] + positions: [ElixirSense.Core.State.position_t()], + end_positions: [ElixirSense.Core.State.position_t() | nil], + generated: list(boolean) } - defstruct name: nil, args: [], specs: [], kind: :spec, positions: [] + defstruct name: nil, args: [], specs: [], kind: :spec, positions: [], end_positions: [], generated: [] end defmodule StructInfo do @@ -1052,8 +1055,10 @@ defmodule ElixirSense.Core.State do args: [arg_names], kind: kind, specs: [spec], - generated: Keyword.get(options, :generated, false), - positions: [pos] + generated: [Keyword.get(options, :generated, false)], + positions: [pos], + # no end pos info in AST as of elixir 1.15 + end_positions: [nil] } current_module_variants = get_current_module_variants(state) @@ -1070,6 +1075,8 @@ defmodule ElixirSense.Core.State do %TypeInfo{ ti | positions: [pos | positions], + end_positions: [nil | ti.end_positions], + generated: [Keyword.get(options, :generated, false) | ti.generated], args: [arg_names | args], specs: [spec | specs], # in case there are multiple definitions for nil arity prefer public ones @@ -1085,7 +1092,7 @@ defmodule ElixirSense.Core.State do %__MODULE__{state | types: types} end - def add_spec(%__MODULE__{} = state, type_name, type_args, spec, kind, pos) do + def add_spec(%__MODULE__{} = state, type_name, type_args, spec, kind, pos, options) do arg_names = type_args |> Enum.map(&Macro.to_string/1) @@ -1095,7 +1102,10 @@ defmodule ElixirSense.Core.State do args: [arg_names], specs: [spec], kind: kind, - positions: [pos] + generated: [Keyword.get(options, :generated, false)], + positions: [pos], + # no end pos info in AST as of elixir 1.15 + end_positions: [nil] } current_module_variants = get_current_module_variants(state) @@ -1112,6 +1122,8 @@ defmodule ElixirSense.Core.State do %SpecInfo{ ti | positions: [pos | positions], + end_positions: [nil | ti.end_positions], + generated: [Keyword.get(options, :generated, false) | ti.generated], args: [arg_names | args], specs: [spec | specs] } From 5303d9802da93e64c78f9f119ade36be6d42b484 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Tue, 4 Jul 2023 22:06:54 +0200 Subject: [PATCH 11/16] fix edge case --- lib/elixir_sense/core/metadata_builder.ex | 4 +- .../core/metadata_builder_test.exs | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index b8da9bab..ac22bd1b 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -197,7 +197,9 @@ defmodule ElixirSense.Core.MetadataBuilder do args: args, specs: specs, kind: :callback, - positions: positions + positions: positions, + end_positions: Enum.map(positions, fn _ -> nil end), + generated: Enum.map(positions, fn _ -> true end), } spec = %State.SpecInfo{specs: specs} -> diff --git a/test/elixir_sense/core/metadata_builder_test.exs b/test/elixir_sense/core/metadata_builder_test.exs index eb51dd4e..4c06e1bb 100644 --- a/test/elixir_sense/core/metadata_builder_test.exs +++ b/test/elixir_sense/core/metadata_builder_test.exs @@ -2831,6 +2831,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :callback, name: :with_spec, positions: [{3, 3}], + end_positions: [nil], + generated: [false], specs: [ "@callback with_spec(t, boolean) :: number", "@spec with_spec(t, boolean) :: number" @@ -2841,6 +2843,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :callback, name: :with_spec, positions: [{3, 3}, {2, 3}], + end_positions: [nil, nil], + generated: [false, false], specs: [ "@callback with_spec(t, boolean) :: number", "@callback with_spec(t, integer) :: String.t", @@ -2853,6 +2857,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :callback, name: :without_spec, positions: [{6, 3}], + end_positions: [nil], + generated: [true], specs: ["@callback without_spec(t, integer) :: term"] }, {Proto, :without_spec, 2} => %ElixirSense.Core.State.SpecInfo{ @@ -2860,6 +2866,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :callback, name: :without_spec, positions: [{6, 3}], + end_positions: [nil], + generated: [true], specs: ["@callback without_spec(t, integer) :: term"] } } @@ -4458,6 +4466,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :type, name: :no_arg_no_parens, positions: [{2, 3}], + end_positions: [nil], + generated: [false], specs: ["@type no_arg_no_parens :: integer"] }, {My, :no_arg_no_parens, nil} => %ElixirSense.Core.State.TypeInfo{ @@ -4465,6 +4475,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :type, name: :no_arg_no_parens, positions: [{2, 3}], + end_positions: [nil], + generated: [false], specs: ["@type no_arg_no_parens :: integer"] }, {My, :no_args, 0} => %ElixirSense.Core.State.TypeInfo{ @@ -4472,6 +4484,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :typep, name: :no_args, positions: [{3, 3}], + end_positions: [nil], + generated: [false], specs: ["@typep no_args :: integer"] }, {My, :no_args, nil} => %ElixirSense.Core.State.TypeInfo{ @@ -4479,6 +4493,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :typep, name: :no_args, positions: [{3, 3}], + end_positions: [nil], + generated: [false], specs: ["@typep no_args :: integer"] }, {My, :overloaded, 0} => %ElixirSense.Core.State.TypeInfo{ @@ -4486,12 +4502,16 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :type, name: :overloaded, positions: [{5, 3}], + end_positions: [nil], + generated: [false], specs: ["@type overloaded :: {}"] }, {My, :overloaded, 1} => %ElixirSense.Core.State.TypeInfo{ kind: :type, name: :overloaded, positions: [{6, 3}], + end_positions: [nil], + generated: [false], args: [["a"]], specs: ["@type overloaded(a) :: {a}"] }, @@ -4499,6 +4519,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :type, name: :overloaded, positions: [{6, 3}, {5, 3}], + end_positions: [nil, nil], + generated: [false, false], args: [["a"], []], specs: ["@type overloaded(a) :: {a}", "@type overloaded :: {}"] }, @@ -4506,6 +4528,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :opaque, name: :with_args, positions: [{4, 3}], + end_positions: [nil], + generated: [false], args: [["a", "b"]], specs: ["@opaque with_args(a, b) :: {a, b}"] }, @@ -4513,6 +4537,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :opaque, name: :with_args, positions: [{4, 3}], + end_positions: [nil], + generated: [false], args: [["a", "b"]], specs: ["@opaque with_args(a, b) :: {a, b}"] } @@ -4551,6 +4577,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :type, name: :t, positions: [{1, 1}], + end_positions: [nil], + generated: [true], specs: ["@type t :: term"] }, {Proto, :t, 0} => %ElixirSense.Core.State.TypeInfo{ @@ -4558,6 +4586,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :type, name: :t, positions: [{1, 1}], + end_positions: [nil], + generated: [true], specs: ["@type t :: term"] } } @@ -4584,6 +4614,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do kind: :spec, name: :abc, positions: [{3, 3}], + end_positions: [nil], + generated: [false], specs: ["@spec abc :: reference"] }, {Proto, :abc, nil} => %ElixirSense.Core.State.SpecInfo{ @@ -4591,6 +4623,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :abc, args: [[], []], positions: [{3, 3}, {2, 3}], + end_positions: [nil, nil], + generated: [false, false], specs: ["@spec abc :: reference", "@spec abc :: atom | integer"] }, {Proto, :my, 1} => %ElixirSense.Core.State.SpecInfo{ @@ -4598,6 +4632,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :my, args: [["a :: integer"]], positions: [{4, 3}], + end_positions: [nil], + generated: [false], specs: ["@callback my(a :: integer) :: atom"] }, {Proto, :my, nil} => %ElixirSense.Core.State.SpecInfo{ @@ -4605,6 +4641,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :my, args: [["a :: integer"]], positions: [{4, 3}], + end_positions: [nil], + generated: [false], specs: ["@callback my(a :: integer) :: atom"] }, {Proto, :other, 1} => %ElixirSense.Core.State.SpecInfo{ @@ -4612,6 +4650,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :other, args: [["x"]], positions: [{5, 3}], + end_positions: [nil], + generated: [false], specs: ["@macrocallback other(x) :: Macro.t when x: integer"] }, {Proto, :other, nil} => %ElixirSense.Core.State.SpecInfo{ @@ -4619,6 +4659,8 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :other, args: [["x"]], positions: [{5, 3}], + end_positions: [nil], + generated: [false], specs: ["@macrocallback other(x) :: Macro.t when x: integer"] } } From 9c790ac5b1e74d7041650144aa3b4943c2a1f48d Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Tue, 4 Jul 2023 23:04:06 +0200 Subject: [PATCH 12/16] fix another edge case --- lib/elixir_sense.ex | 2 +- lib/elixir_sense/core/metadata.ex | 62 ++++++++++++++++-------- test/elixir_sense/core/metadata_test.exs | 28 +++++------ 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/lib/elixir_sense.ex b/lib/elixir_sense.ex index 6aafb761..09ea699f 100644 --- a/lib/elixir_sense.ex +++ b/lib/elixir_sense.ex @@ -231,7 +231,7 @@ defmodule ElixirSense do cursor_context = %{ text_before: text_before, text_after: text_after, - at_module_body?: Metadata.at_module_body?(buffer_file_metadata, env, {line, column}) + at_module_body?: Metadata.at_module_body?(buffer_file_metadata, env) } Suggestion.find(hint, env, buffer_file_metadata, cursor_context, module_store, opts) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 77f34465..fbfbc8aa 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -57,10 +57,16 @@ defmodule ElixirSense.Core.Metadata do {{begin_line, begin_column}, {end_line, end_column}, _} when ((line > begin_line or line == begin_line and column > begin_column) and (line < end_line or line == end_line and column < end_column)) -> - {begin_line, begin_column} + {{begin_line, begin_column}, {end_line, end_column}} {{begin_line, begin_column}, nil, _} when (line > begin_line or line == begin_line and column > begin_column) -> - {begin_line, begin_column} + case find_closest_ending(all_scopes, {begin_line, begin_column}) do + nil -> {{begin_line, begin_column}, nil} + {end_line, end_column} -> + if (line < end_line or line == end_line and column < end_column) do + {{begin_line, begin_column}, {end_line, end_column}} + end + end _ -> nil end) |> Enum.filter(& &1 != nil) @@ -71,13 +77,13 @@ defmodule ElixirSense.Core.Metadata do end end) |> Enum.filter(& &1 != nil) - |> Enum.max_by(fn {_key, _type, begin_position} -> + |> Enum.max_by(fn {_key, _type, {begin_position, _end_position}} -> begin_position end, fn -> nil end) # |> dbg() case closest_scope do - {key, type, {begin_line, _begin_column}} -> + {key, type, {{begin_line, _begin_column}, _}} -> metadata.lines_to_env |> Enum.filter(fn {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> @@ -98,27 +104,41 @@ defmodule ElixirSense.Core.Metadata do |> elem(1) end - @spec at_module_body?(__MODULE__.t(), State.Env.t(), {pos_integer, pos_integer}) :: boolean() - def at_module_body?(%__MODULE__{} = metadata, env, {line, column}) do - modules = metadata.mods_funs_to_positions - |> Enum.filter(&match?({{module, nil, nil}, _} when is_atom(module), &1)) - - found_module_with_end_position = modules - |> Enum.any?(fn {_, %State.ModFunInfo{positions: positions, end_positions: end_positions}} -> - Enum.zip(positions, end_positions) - |> Enum.any?(fn - {{begin_line, begin_column}, {end_line, end_column}} -> - (line > begin_line or line == begin_line and column > begin_column) and - (line < end_line or line == end_line and column < end_column) - {_, nil} -> false - end) + defp find_closest_ending(all_scopes, {line, column}) do + all_scopes + |> Enum.map(fn + {{_, fun, nil}, _} when fun != nil -> nil + {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> + Enum.zip([positions, end_positions, generated]) + |> Enum.map(fn + {_, _, true} -> nil + {{begin_line, begin_column}, {end_line, end_column}, _} -> + if {begin_line, begin_column} > {line, column} do + {begin_line, begin_column} + else + if {end_line, end_column} > {line, column} do + {end_line, end_column} + end + end + {{begin_line, begin_column}, nil, _} -> + if {begin_line, begin_column} > {line, column} do + {begin_line, begin_column} + end + end) + |> Enum.filter(& &1 != nil) + |> Enum.min(fn -> nil end) end) + |> Enum.filter(& &1 != nil) + |> Enum.min(fn -> nil end) + end - found_module_with_end_position and match?(atom when is_atom(atom), env.scope) + @spec at_module_body?(__MODULE__.t(), State.Env.t()) :: boolean() + def at_module_body?(%__MODULE__{} = metadata, env) do + is_atom(env.scope) end - def get_position_to_insert_alias(%__MODULE__{} = metadata, line) do - env = get_env(metadata, line) + def get_position_to_insert_alias(%__MODULE__{} = metadata, {line, column}) do + env = get_env(metadata, {line, column}) module = env.module cond do diff --git a/test/elixir_sense/core/metadata_test.exs b/test/elixir_sense/core/metadata_test.exs index 5d03b17c..c00cfba0 100644 --- a/test/elixir_sense/core/metadata_test.exs +++ b/test/elixir_sense/core/metadata_test.exs @@ -114,28 +114,28 @@ defmodule ElixirSense.Core.MetadataTest do metadata = Parser.parse_string(code, true, true, 1) - env = Metadata.get_env(metadata, 1) + env = Metadata.get_env(metadata, {1, 22}) assert Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 2) - assert Metadata.at_module_body?(metadata, env) + env = Metadata.get_env(metadata, {2, 20}) + refute Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 8) + env = Metadata.get_env(metadata, {8, 13}) assert Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 5) + env = Metadata.get_env(metadata, {5, 5}) refute Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 11) - refute Metadata.at_module_body?(metadata, env) + env = Metadata.get_env(metadata, {11, 5}) + assert Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 14) + env = Metadata.get_env(metadata, {14, 18}) refute Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 16) + env = Metadata.get_env(metadata, {16, 10}) refute Metadata.at_module_body?(metadata, env) - env = Metadata.get_env(metadata, 17) + env = Metadata.get_env(metadata, {17, 12}) refute Metadata.at_module_body?(metadata, env) end @@ -159,13 +159,13 @@ defmodule ElixirSense.Core.MetadataTest do line_number = 5 metadata = Parser.parse_string(code, true, true, line_number) - position = Metadata.get_position_to_insert_alias(metadata, line_number) + position = Metadata.get_position_to_insert_alias(metadata, {line_number, 6}) assert {2, 3} == position line_number = 11 metadata = Parser.parse_string(code, true, true, line_number) - position = Metadata.get_position_to_insert_alias(metadata, line_number) + position = Metadata.get_position_to_insert_alias(metadata, {line_number, 8}) assert {9, 5} == position end @@ -185,7 +185,7 @@ defmodule ElixirSense.Core.MetadataTest do line_number = 7 metadata = Parser.parse_string(code, true, true, line_number) - position = Metadata.get_position_to_insert_alias(metadata, line_number) + position = Metadata.get_position_to_insert_alias(metadata, {line_number, 6}) assert {5, 3} == position end @@ -201,7 +201,7 @@ defmodule ElixirSense.Core.MetadataTest do line_number = 3 metadata = Parser.parse_string(code, true, true, line_number) - position = Metadata.get_position_to_insert_alias(metadata, line_number) + position = Metadata.get_position_to_insert_alias(metadata, {line_number, 6}) assert {2, 3} == position end From d3932298d451a14558954e05361937c14242288d Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Tue, 4 Jul 2023 23:04:39 +0200 Subject: [PATCH 13/16] format --- lib/elixir_sense/core/metadata.ex | 159 ++++--- lib/elixir_sense/core/metadata_builder.ex | 6 +- lib/elixir_sense/core/state.ex | 26 +- lib/elixir_sense/core/surround_context.ex | 4 +- .../providers/suggestion/complete.ex | 6 +- .../providers/suggestion/reducers/common.ex | 1 + .../core/metadata_builder_test.exs | 42 +- .../providers/suggestion/complete_test.exs | 404 +++++++++--------- test/elixir_sense/signature_test.exs | 2 +- test/elixir_sense/suggestions_test.exs | 4 +- 10 files changed, 354 insertions(+), 300 deletions(-) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index fbfbc8aa..40a70e6f 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -43,93 +43,122 @@ defmodule ElixirSense.Core.Metadata do @spec get_env(__MODULE__.t(), {pos_integer, pos_integer}) :: State.Env.t() def get_env(%__MODULE__{} = metadata, {line, column}) do - all_scopes = Enum.to_list(metadata.types) ++ - Enum.to_list(metadata.specs) ++ - Enum.to_list(metadata.mods_funs_to_positions) + all_scopes = + Enum.to_list(metadata.types) ++ + Enum.to_list(metadata.specs) ++ + Enum.to_list(metadata.mods_funs_to_positions) - closest_scope = all_scopes - |> Enum.map(fn - {{_, fun, nil}, _} when fun != nil -> nil - {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> - closest_scope = Enum.zip([positions, end_positions, generated]) + closest_scope = + all_scopes |> Enum.map(fn - {_, _, true} -> nil - {{begin_line, begin_column}, {end_line, end_column}, _} when - ((line > begin_line or line == begin_line and column > begin_column) and - (line < end_line or line == end_line and column < end_column)) -> - {{begin_line, begin_column}, {end_line, end_column}} - {{begin_line, begin_column}, nil, _} when - (line > begin_line or line == begin_line and column > begin_column) -> - case find_closest_ending(all_scopes, {begin_line, begin_column}) do - nil -> {{begin_line, begin_column}, nil} - {end_line, end_column} -> - if (line < end_line or line == end_line and column < end_column) do - {{begin_line, begin_column}, {end_line, end_column}} + {{_, fun, nil}, _} when fun != nil -> + nil + + {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> + closest_scope = + Enum.zip([positions, end_positions, generated]) + |> Enum.map(fn + {_, _, true} -> + nil + + {{begin_line, begin_column}, {end_line, end_column}, _} + when (line > begin_line or (line == begin_line and column > begin_column)) and + (line < end_line or (line == end_line and column < end_column)) -> + {{begin_line, begin_column}, {end_line, end_column}} + + {{begin_line, begin_column}, nil, _} + when line > begin_line or (line == begin_line and column > begin_column) -> + case find_closest_ending(all_scopes, {begin_line, begin_column}) do + nil -> + {{begin_line, begin_column}, nil} + + {end_line, end_column} -> + if line < end_line or (line == end_line and column < end_column) do + {{begin_line, begin_column}, {end_line, end_column}} + end end - end - _ -> nil + + _ -> + nil + end) + |> Enum.filter(&(&1 != nil)) + |> Enum.max(fn -> nil end) + + if closest_scope do + {key, type, closest_scope} + end end) - |> Enum.filter(& &1 != nil) - |> Enum.max(fn -> nil end) + |> Enum.filter(&(&1 != nil)) + |> Enum.max_by( + fn {_key, _type, {begin_position, _end_position}} -> + begin_position + end, + fn -> nil end + ) - if closest_scope do - {key, type, closest_scope} - end - end) - |> Enum.filter(& &1 != nil) - |> Enum.max_by(fn {_key, _type, {begin_position, _end_position}} -> - begin_position - end, fn -> nil end) # |> dbg() case closest_scope do {key, type, {{begin_line, _begin_column}, _}} -> metadata.lines_to_env |> Enum.filter(fn - {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> - case {key, type} do - {{module, nil, nil}, _} -> - module in env.module_variants and (is_atom(env.scope)) - {{module, fun, arity}, State.ModFunInfo} -> - module in env.module_variants and env.scope == {fun, arity} - {{module, fun, arity}, type} when type in [State.TypeInfo, State.SpecInfo] -> - module in env.module_variants and env.scope == {:typespec, fun, arity} - end - _ -> false + {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> + case {key, type} do + {{module, nil, nil}, _} -> + module in env.module_variants and is_atom(env.scope) + + {{module, fun, arity}, State.ModFunInfo} -> + module in env.module_variants and env.scope == {fun, arity} + + {{module, fun, arity}, type} when type in [State.TypeInfo, State.SpecInfo] -> + module in env.module_variants and env.scope == {:typespec, fun, arity} + end + + _ -> + false end) - # |> dbg() - nil -> metadata.lines_to_env + + # |> dbg() + nil -> + metadata.lines_to_env end - |> Enum.max_by(fn {metadata_line, _env} -> metadata_line end, fn -> {line, State.default_env()} end) + |> Enum.max_by(fn {metadata_line, _env} -> metadata_line end, fn -> + {line, State.default_env()} + end) |> elem(1) end defp find_closest_ending(all_scopes, {line, column}) do all_scopes |> Enum.map(fn - {{_, fun, nil}, _} when fun != nil -> nil - {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> - Enum.zip([positions, end_positions, generated]) - |> Enum.map(fn - {_, _, true} -> nil - {{begin_line, begin_column}, {end_line, end_column}, _} -> - if {begin_line, begin_column} > {line, column} do - {begin_line, begin_column} - else - if {end_line, end_column} > {line, column} do - {end_line, end_column} + {{_, fun, nil}, _} when fun != nil -> + nil + + {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> + Enum.zip([positions, end_positions, generated]) + |> Enum.map(fn + {_, _, true} -> + nil + + {{begin_line, begin_column}, {end_line, end_column}, _} -> + if {begin_line, begin_column} > {line, column} do + {begin_line, begin_column} + else + if {end_line, end_column} > {line, column} do + {end_line, end_column} + end + end + + {{begin_line, begin_column}, nil, _} -> + if {begin_line, begin_column} > {line, column} do + {begin_line, begin_column} end - end - {{begin_line, begin_column}, nil, _} -> - if {begin_line, begin_column} > {line, column} do - {begin_line, begin_column} - end end) - |> Enum.filter(& &1 != nil) - |> Enum.min(fn -> nil end) + |> Enum.filter(&(&1 != nil)) + |> Enum.min(fn -> nil end) end) - |> Enum.filter(& &1 != nil) - |> Enum.min(fn -> nil end) + |> Enum.filter(&(&1 != nil)) + |> Enum.min(fn -> nil end) end @spec at_module_body?(__MODULE__.t(), State.Env.t()) :: boolean() diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index ac22bd1b..19e652df 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -94,7 +94,7 @@ defmodule ElixirSense.Core.MetadataBuilder do state |> maybe_add_protocol_implementation(module) |> add_namespace(module) - |> add_current_module_to_index(position, end_position, [generated: state.generated]) + |> add_current_module_to_index(position, end_position, generated: state.generated) |> alias_submodule(module) |> new_alias_scope |> new_attributes_scope @@ -123,7 +123,7 @@ defmodule ElixirSense.Core.MetadataBuilder do position, nil, kind, - [generated: true] + generated: true ) end) @@ -199,7 +199,7 @@ defmodule ElixirSense.Core.MetadataBuilder do kind: :callback, positions: positions, end_positions: Enum.map(positions, fn _ -> nil end), - generated: Enum.map(positions, fn _ -> true end), + generated: Enum.map(positions, fn _ -> true end) } spec = %State.SpecInfo{specs: specs} -> diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index 8877d50d..4ee59633 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -154,7 +154,13 @@ defmodule ElixirSense.Core.State do end_positions: [ElixirSense.Core.State.position_t() | nil], generated: list(boolean) } - defstruct name: nil, args: [], specs: [], kind: :type, positions: [], end_positions: [], generated: [] + defstruct name: nil, + args: [], + specs: [], + kind: :type, + positions: [], + end_positions: [], + generated: [] end defmodule SpecInfo do @@ -170,7 +176,13 @@ defmodule ElixirSense.Core.State do end_positions: [ElixirSense.Core.State.position_t() | nil], generated: list(boolean) } - defstruct name: nil, args: [], specs: [], kind: :spec, positions: [], end_positions: [], generated: [] + defstruct name: nil, + args: [], + specs: [], + kind: :spec, + positions: [], + end_positions: [], + generated: [] end defmodule StructInfo do @@ -716,7 +728,15 @@ defmodule ElixirSense.Core.State do def add_module_to_index(%__MODULE__{} = state, module, position, end_position, options) when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do # TODO :defprotocol, :defimpl? - add_mod_fun_to_position(state, {module, nil, nil}, position, end_position, nil, :defmodule, options) + add_mod_fun_to_position( + state, + {module, nil, nil}, + position, + end_position, + nil, + :defmodule, + options + ) end # TODO require end position diff --git a/lib/elixir_sense/core/surround_context.ex b/lib/elixir_sense/core/surround_context.ex index 58e166dc..236a65a0 100644 --- a/lib/elixir_sense/core/surround_context.ex +++ b/lib/elixir_sense/core/surround_context.ex @@ -11,7 +11,7 @@ defmodule ElixirSense.Core.SurroundContext do {inside_dot_to_binding(inside_dot, current_module), :"#{charlist}"} end - def to_binding({:local_or_var, '__MODULE__'}, current_module) do + def to_binding({:local_or_var, ~c"__MODULE__"}, current_module) do if current_module not in [nil, Elixir] do {:atom, current_module} end @@ -71,7 +71,7 @@ defmodule ElixirSense.Core.SurroundContext do end defp inside_dot_to_binding( - {:alias, {:local_or_var, '__MODULE__'}, inside_charlist}, + {:alias, {:local_or_var, ~c"__MODULE__"}, inside_charlist}, current_module ) do if current_module not in [nil, Elixir] do diff --git a/lib/elixir_sense/providers/suggestion/complete.ex b/lib/elixir_sense/providers/suggestion/complete.ex index 0b5514f3..1204d142 100644 --- a/lib/elixir_sense/providers/suggestion/complete.ex +++ b/lib/elixir_sense/providers/suggestion/complete.ex @@ -224,7 +224,7 @@ defmodule ElixirSense.Providers.Suggestion.Complete do end # elixir >= 1.14 - defp expand_dot_path({:var, '__MODULE__'}, env) do + defp expand_dot_path({:var, ~c"__MODULE__"}, env) do if env.scope_module != nil and Introspection.elixir_module?(env.scope_module) do {:ok, {:atom, env.scope_module}} else @@ -252,7 +252,7 @@ defmodule ElixirSense.Providers.Suggestion.Complete do # elixir >= 1.14 defp expand_dot_path({:alias, {:local_or_var, var}, hint}, env) do case var do - '__MODULE__' -> + ~c"__MODULE__" -> alias_suffix = hint |> List.to_string() |> String.split(".") alias = [{:__MODULE__, [], nil} | alias_suffix] |> value_from_alias(env) @@ -483,7 +483,7 @@ defmodule ElixirSense.Providers.Suggestion.Complete do |> format_expansion() end - defp expand_prefixed_aliases({:local_or_var, '__MODULE__'}, hint, env, only_structs) do + defp expand_prefixed_aliases({:local_or_var, ~c"__MODULE__"}, hint, env, only_structs) do if env.scope_module != nil and Introspection.elixir_module?(env.scope_module) do expand_aliases("#{env.scope_module}.#{hint}", env, only_structs, []) else diff --git a/lib/elixir_sense/providers/suggestion/reducers/common.ex b/lib/elixir_sense/providers/suggestion/reducers/common.ex index 499db98f..91d8081b 100644 --- a/lib/elixir_sense/providers/suggestion/reducers/common.ex +++ b/lib/elixir_sense/providers/suggestion/reducers/common.ex @@ -185,6 +185,7 @@ defmodule ElixirSense.Providers.Suggestion.Reducers.Common do else hint end + Complete.complete(hint, env, opts) end end diff --git a/test/elixir_sense/core/metadata_builder_test.exs b/test/elixir_sense/core/metadata_builder_test.exs index 4c06e1bb..adfcb326 100644 --- a/test/elixir_sense/core/metadata_builder_test.exs +++ b/test/elixir_sense/core/metadata_builder_test.exs @@ -2832,7 +2832,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :with_spec, positions: [{3, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: [ "@callback with_spec(t, boolean) :: number", "@spec with_spec(t, boolean) :: number" @@ -2844,7 +2844,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :with_spec, positions: [{3, 3}, {2, 3}], end_positions: [nil, nil], - generated: [false, false], + generated: [false, false], specs: [ "@callback with_spec(t, boolean) :: number", "@callback with_spec(t, integer) :: String.t", @@ -2858,7 +2858,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :without_spec, positions: [{6, 3}], end_positions: [nil], - generated: [true], + generated: [true], specs: ["@callback without_spec(t, integer) :: term"] }, {Proto, :without_spec, 2} => %ElixirSense.Core.State.SpecInfo{ @@ -2867,7 +2867,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :without_spec, positions: [{6, 3}], end_positions: [nil], - generated: [true], + generated: [true], specs: ["@callback without_spec(t, integer) :: term"] } } @@ -4467,7 +4467,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :no_arg_no_parens, positions: [{2, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@type no_arg_no_parens :: integer"] }, {My, :no_arg_no_parens, nil} => %ElixirSense.Core.State.TypeInfo{ @@ -4476,7 +4476,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :no_arg_no_parens, positions: [{2, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@type no_arg_no_parens :: integer"] }, {My, :no_args, 0} => %ElixirSense.Core.State.TypeInfo{ @@ -4485,7 +4485,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :no_args, positions: [{3, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@typep no_args :: integer"] }, {My, :no_args, nil} => %ElixirSense.Core.State.TypeInfo{ @@ -4494,7 +4494,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :no_args, positions: [{3, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@typep no_args :: integer"] }, {My, :overloaded, 0} => %ElixirSense.Core.State.TypeInfo{ @@ -4503,7 +4503,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :overloaded, positions: [{5, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@type overloaded :: {}"] }, {My, :overloaded, 1} => %ElixirSense.Core.State.TypeInfo{ @@ -4511,7 +4511,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :overloaded, positions: [{6, 3}], end_positions: [nil], - generated: [false], + generated: [false], args: [["a"]], specs: ["@type overloaded(a) :: {a}"] }, @@ -4520,7 +4520,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :overloaded, positions: [{6, 3}, {5, 3}], end_positions: [nil, nil], - generated: [false, false], + generated: [false, false], args: [["a"], []], specs: ["@type overloaded(a) :: {a}", "@type overloaded :: {}"] }, @@ -4529,7 +4529,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :with_args, positions: [{4, 3}], end_positions: [nil], - generated: [false], + generated: [false], args: [["a", "b"]], specs: ["@opaque with_args(a, b) :: {a, b}"] }, @@ -4538,7 +4538,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :with_args, positions: [{4, 3}], end_positions: [nil], - generated: [false], + generated: [false], args: [["a", "b"]], specs: ["@opaque with_args(a, b) :: {a, b}"] } @@ -4578,7 +4578,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :t, positions: [{1, 1}], end_positions: [nil], - generated: [true], + generated: [true], specs: ["@type t :: term"] }, {Proto, :t, 0} => %ElixirSense.Core.State.TypeInfo{ @@ -4587,7 +4587,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :t, positions: [{1, 1}], end_positions: [nil], - generated: [true], + generated: [true], specs: ["@type t :: term"] } } @@ -4615,7 +4615,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do name: :abc, positions: [{3, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@spec abc :: reference"] }, {Proto, :abc, nil} => %ElixirSense.Core.State.SpecInfo{ @@ -4624,7 +4624,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [[], []], positions: [{3, 3}, {2, 3}], end_positions: [nil, nil], - generated: [false, false], + generated: [false, false], specs: ["@spec abc :: reference", "@spec abc :: atom | integer"] }, {Proto, :my, 1} => %ElixirSense.Core.State.SpecInfo{ @@ -4633,7 +4633,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [["a :: integer"]], positions: [{4, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@callback my(a :: integer) :: atom"] }, {Proto, :my, nil} => %ElixirSense.Core.State.SpecInfo{ @@ -4642,7 +4642,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [["a :: integer"]], positions: [{4, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@callback my(a :: integer) :: atom"] }, {Proto, :other, 1} => %ElixirSense.Core.State.SpecInfo{ @@ -4651,7 +4651,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [["x"]], positions: [{5, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@macrocallback other(x) :: Macro.t when x: integer"] }, {Proto, :other, nil} => %ElixirSense.Core.State.SpecInfo{ @@ -4660,7 +4660,7 @@ defmodule ElixirSense.Core.MetadataBuilderTest do args: [["x"]], positions: [{5, 3}], end_positions: [nil], - generated: [false], + generated: [false], specs: ["@macrocallback other(x) :: Macro.t when x: integer"] } } diff --git a/test/elixir_sense/providers/suggestion/complete_test.exs b/test/elixir_sense/providers/suggestion/complete_test.exs index 29907f02..b546fbf3 100644 --- a/test/elixir_sense/providers/suggestion/complete_test.exs +++ b/test/elixir_sense/providers/suggestion/complete_test.exs @@ -44,7 +44,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do type: :module, metadata: metadata } - ] = expand(':zl') + ] = expand(~c":zl") if ExUnitConfig.erlang_eep48_supported() do assert "This module provides an API for the zlib library" <> _ = summary @@ -53,37 +53,37 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do end test "erlang module no completion" do - assert expand(':unknown') == [] + assert expand(~c":unknown") == [] end test "erlang module multiple values completion" do - list = expand(':logger') + list = expand(~c":logger") assert list |> Enum.find(&(&1.name == ":logger")) assert list |> Enum.find(&(&1.name == ":logger_proxy")) end test "erlang root completion" do - list = expand(':') + list = expand(~c":") assert is_list(list) assert list |> Enum.find(&(&1.name == ":lists")) assert [] == list |> Enum.filter(&(&1.name |> String.contains?("Elixir.List"))) end test "elixir proxy" do - list = expand('E') + list = expand(~c"E") assert list |> Enum.find(&(&1.name == "Elixir" and &1.full_name == "Elixir.Elixir")) end test "elixir completion" do - assert [_ | _] = expand('En') + assert [_ | _] = expand(~c"En") assert [%{name: "Enumerable", full_name: "Enumerable", subtype: :protocol, type: :module}] = - expand('Enumera') + expand(~c"Enumera") end test "elixir module completion with @moduledoc false" do assert [%{name: "ModuleWithDocFalse", summary: ""}] = - expand('ElixirSenseExample.ModuleWithDocFals') + expand(~c"ElixirSenseExample.ModuleWithDocFals") end test "elixir function completion with @doc false" do @@ -124,11 +124,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "", type: :function } - ] = expand('ElixirSenseExample.ModuleWithDocs.some_fun_') + ] = expand(~c"ElixirSenseExample.ModuleWithDocs.some_fun_") end test "elixir completion with self" do - assert [%{name: "Enumerable", subtype: :protocol}] = expand('Enumerable') + assert [%{name: "Enumerable", subtype: :protocol}] = expand(~c"Enumerable") end test "elixir completion macro with default args" do @@ -160,7 +160,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "some macro with default arg\n", type: :macro } - ] = expand('ElixirSenseExample.BehaviourWithMacrocallback.Impl.wit') + ] = expand(~c"ElixirSenseExample.BehaviourWithMacrocallback.Impl.wit") end test "elixir completion on modules from load path" do @@ -168,19 +168,19 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do %{name: "Stream", subtype: :struct, type: :module}, %{name: "String", subtype: nil, type: :module}, %{name: "StringIO", subtype: nil, type: :module} - ] = expand('Str') |> Enum.filter(&(&1.name |> String.starts_with?("Str"))) + ] = expand(~c"Str") |> Enum.filter(&(&1.name |> String.starts_with?("Str"))) assert [ %{name: "Macro"}, %{name: "Map"}, %{name: "MapSet"}, %{name: "MatchError"} - ] = expand('Ma') |> Enum.filter(&(&1.name |> String.starts_with?("Ma"))) + ] = expand(~c"Ma") |> Enum.filter(&(&1.name |> String.starts_with?("Ma"))) assert [%{name: "Dict"}] = - expand('Dic') |> Enum.filter(&(&1.name |> String.starts_with?("Dic"))) + expand(~c"Dic") |> Enum.filter(&(&1.name |> String.starts_with?("Dic"))) - assert suggestions = expand('Ex') + assert suggestions = expand(~c"Ex") assert Enum.any?(suggestions, &(&1.name == "ExUnit")) assert Enum.any?(suggestions, &(&1.name == "Exception")) end @@ -201,7 +201,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do end # IEx version asserts expansion on Sample._ but we also include :__info__ and there is more than 1 match - assert [%{name: "__bar__"}] = expand('Sample.__b') + assert [%{name: "__bar__"}] = expand(~c"Sample.__b") after File.rm("Elixir.Sample.beam") :code.purge(Sample) @@ -216,7 +216,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do File.write!("ElixirSense.Providers.Suggestion.CompleteTest.Sample.beam", bytecode) - assert [%{name: "foo"}] = expand('ElixirSense.Providers.Suggestion.CompleteTest.Sample.foo') + assert [%{name: "foo"}] = expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.Sample.foo") Code.compiler_options(ignore_module_conflict: true) @@ -226,7 +226,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do end assert [%{name: "foo"}, %{name: "foobar"}] = - expand('ElixirSense.Providers.Suggestion.CompleteTest.Sample.foo') + expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.Sample.foo") after File.rm("ElixirSense.Providers.Suggestion.CompleteTest.Sample.beam") Code.compiler_options(ignore_module_conflict: false) @@ -235,10 +235,10 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do end test "elixir no completion" do - assert expand('.') == [] - assert expand('Xyz') == [] - assert expand('x.Foo') == [] - assert expand('x.Foo.get_by') == [] + assert expand(~c".") == [] + assert expand(~c"Xyz") == [] + assert expand(~c"x.Foo") == [] + assert expand(~c"x.Foo.get_by") == [] # assert expand('@foo.bar') == {:no, '', []} end @@ -249,9 +249,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do full_name: "Access", summary: "Key-based access to data structures." } - ] = expand('Elixir.Acce') + ] = expand(~c"Elixir.Acce") - assert [_ | _] = expand('Elixir.') + assert [_ | _] = expand(~c"Elixir.") end test "elixir submodule completion" do @@ -263,7 +263,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "The `String.Chars` protocol is responsible for\nconverting a structure to a binary (only if applicable)." } - ] = expand('String.Cha') + ] = expand(~c"String.Cha") end @tag requires_elixir_1_14: true @@ -276,7 +276,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "The `String.Chars` protocol is responsible for\nconverting a structure to a binary (only if applicable)." } - ] = expand('__MODULE__.Cha', %Env{scope_module: String}) + ] = expand(~c"__MODULE__.Cha", %Env{scope_module: String}) end @tag requires_elixir_1_14: true @@ -290,7 +290,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do "The `String.Chars` protocol is responsible for\nconverting a structure to a binary (only if applicable)." } ] = - expand('@my_attr.Cha', %Env{ + expand(~c"@my_attr.Cha", %Env{ attributes: [ %AttributeInfo{ name: :my_attr, @@ -322,7 +322,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do required_alias: "ElixirSense.Providers.ReferencesTest.Modules.CallerWithAliasesAndImports" } - ] = expand('Char', %Env{}, required_alias: true) + ] = expand(~c"Char", %Env{}, required_alias: true) end test "does not suggest required_alias when alias already exists" do @@ -330,36 +330,36 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do aliases: [{MyChars, String.Chars}] } - results = expand('Char', env, required_alias: true) + results = expand(~c"Char", env, required_alias: true) refute Enum.find(results, fn expansion -> expansion[:required_alias] == String.Chars end) end test "elixir submodule no completion" do - assert expand('IEx.Xyz') == [] + assert expand(~c"IEx.Xyz") == [] end test "function completion" do - assert [%{name: "version", origin: "System"}] = expand('System.ve') - assert [%{name: "fun2ms", origin: ":ets"}] = expand(':ets.fun2') + assert [%{name: "version", origin: "System"}] = expand(~c"System.ve") + assert [%{name: "fun2ms", origin: ":ets"}] = expand(~c":ets.fun2") end @tag requires_elixir_1_14: true test "function completion on __MODULE__" do assert [%{name: "version", origin: "System"}] = - expand('__MODULE__.ve', %Env{scope_module: System}) + expand(~c"__MODULE__.ve", %Env{scope_module: System}) end @tag requires_elixir_1_14: true test "function completion on __MODULE__ submodules" do assert [%{name: "to_string", origin: "String.Chars"}] = - expand('__MODULE__.Chars.to', %Env{scope_module: String}) + expand(~c"__MODULE__.Chars.to", %Env{scope_module: String}) end @tag requires_elixir_1_14: true test "function completion on attribute bound to module" do assert [%{name: "version", origin: "System"}] = - expand('@my_attr.ve', %Env{ + expand(~c"@my_attr.ve", %Env{ attributes: [ %AttributeInfo{ name: :my_attr, @@ -387,10 +387,10 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "Checks if a string contains only printable characters up to `character_limit`." } - ] = expand('String.printable?') + ] = expand(~c"String.printable?") assert [%{name: "printable?", arity: 1}, %{name: "printable?", arity: 2}] = - expand('String.printable?/') + expand(~c"String.printable?/") assert [ %{ @@ -409,7 +409,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do name: "count_until", arity: 3 } - ] = expand('Enum.count') + ] = expand(~c"Enum.count") assert [ %{ @@ -420,28 +420,30 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do name: "count", arity: 2 } - ] = expand('Enum.count/') + ] = expand(~c"Enum.count/") end @tag requires_elixir_1_13: true test "operator completion" do - assert [%{name: "+", arity: 1}, %{name: "+", arity: 2}, %{name: "++", arity: 2}] = expand('+') - assert [%{name: "+", arity: 1}, %{name: "+", arity: 2}] = expand('+/') - assert [%{name: "++", arity: 2}] = expand('++/') + assert [%{name: "+", arity: 1}, %{name: "+", arity: 2}, %{name: "++", arity: 2}] = + expand(~c"+") - assert entries = expand('+ ') + assert [%{name: "+", arity: 1}, %{name: "+", arity: 2}] = expand(~c"+/") + assert [%{name: "++", arity: 2}] = expand(~c"++/") + + assert entries = expand(~c"+ ") assert entries |> Enum.any?(&(&1.name == "div")) end @tag requires_elixir_1_13: true test "sigil completion" do - sigils = expand('~') + sigils = expand(~c"~") assert sigils |> Enum.any?(fn s -> s.name == "~C" end) # We choose not to provide sigil quotations # {:yes, '', sigils} = expand('~r') # assert '"' in sigils # assert '(' in sigils - assert [] == expand('~r') + assert [] == expand(~c"~r") end test "function completion using a variable bound to a module" do @@ -455,7 +457,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } assert [%{name: "printable?", arity: 1}, %{name: "printable?", arity: 2}] = - expand('mod.print', env) + expand(~c"mod.print", env) end test "map atom key completion is supported" do @@ -468,7 +470,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('map.f', env) == + assert expand(~c"map.f", env) == [ %{ name: "foo", @@ -480,9 +482,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert [_ | _] = expand('map.b', env) + assert [_ | _] = expand(~c"map.b", env) - assert expand('map.bar_', env) == + assert expand(~c"map.bar_", env) == [ %{ name: "bar_1", @@ -502,9 +504,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('map.c', env) == [] + assert expand(~c"map.c", env) == [] - assert expand('map.', env) == + assert expand(~c"map.", env) == [ %{ name: "bar_1", @@ -532,7 +534,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('map.foo', env) == [ + assert expand(~c"map.foo", env) == [ %{ call?: true, name: "foo", @@ -573,7 +575,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('struct.h', env) == + assert expand(~c"struct.h", env) == [ %{ call?: true, @@ -585,7 +587,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('other.d', env) == + assert expand(~c"other.d", env) == [ %{ call?: true, @@ -597,7 +599,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('from_metadata.s', env) == + assert expand(~c"from_metadata.s", env) == [ %{ call?: true, @@ -620,7 +622,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('@map.f', env) == + assert expand(~c"@map.f", env) == [ %{ name: "foo", @@ -632,9 +634,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert [_ | _] = expand('@map.b', env) + assert [_ | _] = expand(~c"@map.b", env) - assert expand('@map.bar_', env) == + assert expand(~c"@map.bar_", env) == [ %{ name: "bar_1", @@ -654,9 +656,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('@map.c', env) == [] + assert expand(~c"@map.c", env) == [] - assert expand('@map.', env) == + assert expand(~c"@map.", env) == [ %{ name: "bar_1", @@ -684,7 +686,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('@map.foo', env) == [ + assert expand(~c"@map.foo", env) == [ %{ call?: true, name: "foo", @@ -722,7 +724,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('map.nested.deeply.f', env) == + assert expand(~c"map.nested.deeply.f", env) == [ %{ name: "foo", @@ -734,9 +736,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert [_ | _] = expand('map.nested.deeply.b', env) + assert [_ | _] = expand(~c"map.nested.deeply.b", env) - assert expand('map.nested.deeply.bar_', env) == + assert expand(~c"map.nested.deeply.bar_", env) == [ %{ name: "bar_1", @@ -756,7 +758,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('map.nested.deeply.', env) == + assert expand(~c"map.nested.deeply.", env) == [ %{ name: "bar_1", @@ -800,9 +802,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert [_ | _] = expand('map.nested.deeply.mod.print', env) + assert [_ | _] = expand(~c"map.nested.deeply.mod.print", env) - assert expand('map.nested', env) == + assert expand(~c"map.nested", env) == [ %{ name: "nested", @@ -814,7 +816,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('map.nested.deeply', env) == + assert expand(~c"map.nested.deeply", env) == [ %{ name: "deeply", @@ -826,7 +828,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('map.nested.deeply.foo', env) == [ + assert expand(~c"map.nested.deeply.foo", env) == [ %{ call?: true, name: "foo", @@ -837,8 +839,8 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('map.nested.deeply.c', env) == [] - assert expand('map.a.b.c.f', env) == [] + assert expand(~c"map.nested.deeply.c", env) == [] + assert expand(~c"map.a.b.c.f", env) == [] end test "map string key completion is not supported" do @@ -851,7 +853,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('map.f', env) == [] + assert expand(~c"map.f", env) == [] end test "autocompletion off a bound variable only works for modules and maps" do @@ -864,9 +866,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('num.print', env) == [] - assert expand('map.nested.num.f', env) == [] - assert expand('map.nested.num.key.f', env) == [] + assert expand(~c"num.print", env) == [] + assert expand(~c"map.nested.num.f", env) == [] + assert expand(~c"map.nested.num.key.f", env) == [] end test "autocomplete map fields from call binding" do @@ -883,7 +885,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert [_ | _] = expand('call.print', env) + assert [_ | _] = expand(~c"call.print", env) end test "autocomplete call return binding" do @@ -896,24 +898,24 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert [_ | _] = expand('call.ho', env) - assert [_ | _] = expand('DateTime.utc_now.ho', env) + assert [_ | _] = expand(~c"call.ho", env) + assert [_ | _] = expand(~c"DateTime.utc_now.ho", env) # Code.cursor_context returns :none for those cases # assert {:yes, 'ur', _} = expand('DateTime.utc_now().', env) # assert {:yes, 'ur', _} = expand('DateTime.utc_now().ho', env) end test "autocompletion off of unbound variables is not supported" do - assert expand('other_var.f') == [] - assert expand('a.b.c.d') == [] + assert expand(~c"other_var.f") == [] + assert expand(~c"a.b.c.d") == [] end test "macro completion" do - assert [_ | _] = expand('Kernel.is_') + assert [_ | _] = expand(~c"Kernel.is_") end test "imports completion" do - list = expand('') + list = expand(~c"") assert is_list(list) assert list |> Enum.find(&(&1.name == "unquote")) @@ -924,24 +926,24 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do test "imports completion in call arg" do # local call - list = expand('asd(') + list = expand(~c"asd(") assert is_list(list) assert list |> Enum.find(&(&1.name == "unquote")) - list = expand('asd(un') + list = expand(~c"asd(un") assert is_list(list) assert list |> Enum.find(&(&1.name == "unquote")) # remote call - list = expand('Abc.asd(') + list = expand(~c"Abc.asd(") assert is_list(list) assert list |> Enum.find(&(&1.name == "unquote")) - list = expand('Abc.asd(un') + list = expand(~c"Abc.asd(un") assert is_list(list) assert list |> Enum.find(&(&1.name == "unquote")) @@ -956,7 +958,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do # assert list |> Enum.find(&(&1.name == "unquote")) - list = expand('asd.(un') + list = expand(~c"asd.(un") assert is_list(list) assert list |> Enum.find(&(&1.name == "unquote")) @@ -973,13 +975,13 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "Defines a struct.", type: :macro } - ] = expand('defstru') + ] = expand(~c"defstru") assert [ %{arity: 3, name: "put_elem"}, %{arity: 2, name: "put_in"}, %{arity: 3, name: "put_in"} - ] = expand('put_') + ] = expand(~c"put_") end test "variable name completion" do @@ -997,12 +999,12 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('numb', env) == [%{type: :variable, name: "number"}] + assert expand(~c"numb", env) == [%{type: :variable, name: "number"}] - assert expand('num', env) == + assert expand(~c"num", env) == [%{type: :variable, name: "number"}, %{type: :variable, name: "numeral"}] - assert [%{type: :variable, name: "nothing"} | _] = expand('no', env) + assert [%{type: :variable, name: "nothing"} | _] = expand(~c"no", env) end test "variable name completion after pin" do @@ -1014,8 +1016,8 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('^numb', env) == [%{type: :variable, name: "number"}] - assert expand('^', env) == [%{type: :variable, name: "number"}] + assert expand(~c"^numb", env) == [%{type: :variable, name: "number"}] + assert expand(~c"^", env) == [%{type: :variable, name: "number"}] end test "attribute name completion" do @@ -1034,12 +1036,12 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do scope: {:some, 0} } - assert expand('@numb', env) == [%{type: :attribute, name: "@number"}] + assert expand(~c"@numb", env) == [%{type: :attribute, name: "@number"}] - assert expand('@num', env) == + assert expand(~c"@num", env) == [%{type: :attribute, name: "@number"}, %{type: :attribute, name: "@numeral"}] - assert expand('@', env) == + assert expand(~c"@", env) == [ %{name: "@nothing", type: :attribute}, %{type: :attribute, name: "@number"}, @@ -1063,27 +1065,27 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do scope: Elixir } - assert expand('@befo', env_function) == [] - assert expand('@befo', env_outside_module) == [] + assert expand(~c"@befo", env_function) == [] + assert expand(~c"@befo", env_outside_module) == [] - assert expand('@befo', env_module) == + assert expand(~c"@befo", env_module) == [%{type: :attribute, name: "@before_compile"}] end test "kernel special form completion" do - assert [%{name: "unquote_splicing", origin: "Kernel.SpecialForms"}] = expand('unquote_spl') + assert [%{name: "unquote_splicing", origin: "Kernel.SpecialForms"}] = expand(~c"unquote_spl") end test "completion inside expression" do - assert [_ | _] = expand('1 En') - assert [_ | _] = expand('Test(En') - assert [_] = expand('Test :zl') - assert [_] = expand('[:zl') - assert [_] = expand('{:zl') + assert [_ | _] = expand(~c"1 En") + assert [_ | _] = expand(~c"Test(En") + assert [_] = expand(~c"Test :zl") + assert [_] = expand(~c"[:zl") + assert [_] = expand(~c"{:zl") end test "ampersand completion" do - assert [_ | _] = expand('&Enu') + assert [_ | _] = expand(~c"&Enu") assert [ %{name: "all?", arity: 1}, @@ -1092,7 +1094,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do %{name: "any?", arity: 2}, %{name: "at", arity: 2}, %{name: "at", arity: 3} - ] = expand('&Enum.a') + ] = expand(~c"&Enum.a") assert [ %{name: "all?", arity: 1}, @@ -1101,7 +1103,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do %{name: "any?", arity: 2}, %{name: "at", arity: 2}, %{name: "at", arity: 3} - ] = expand('f = &Enum.a') + ] = expand(~c"f = &Enum.a") end defmodule SublevelTest.LevelA.LevelB do @@ -1109,7 +1111,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do test "elixir completion sublevel" do assert [%{name: "LevelA"}] = - expand('ElixirSense.Providers.Suggestion.CompleteTest.SublevelTest.') + expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.SublevelTest.") end defmodule MyServer do @@ -1123,11 +1125,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do aliases: [{MyList, List}] } - assert [%{name: "MyList"}] = expand('MyL', env) - assert [%{name: "MyList"}] = expand('MyList', env) + assert [%{name: "MyList"}] = expand(~c"MyL", env) + assert [%{name: "MyList"}] = expand(~c"MyList", env) assert [%{arity: 1, name: "to_integer"}, %{arity: 2, name: "to_integer"}] = - expand('MyList.to_integer', env) + expand(~c"MyList.to_integer", env) end test "complete aliases of erlang modules" do @@ -1135,14 +1137,14 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do aliases: [{EList, :lists}] } - assert [%{name: "EList"}] = expand('EL', env) - assert [%{name: "EList"}] = expand('EList', env) + assert [%{name: "EList"}] = expand(~c"EL", env) + assert [%{name: "EList"}] = expand(~c"EList", env) assert [ %{arity: 2, name: "map"}, %{arity: 3, name: "mapfoldl"}, %{arity: 3, name: "mapfoldr"} - ] = expand('EList.map', env) + ] = expand(~c"EList.map", env) end test "complete local funs from scope module" do @@ -1191,7 +1193,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } } - assert [_ | _] = expand('my_f', env) + assert [_ | _] = expand(~c"my_f", env) assert [ %{ @@ -1201,31 +1203,31 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do type: :function, spec: "@spec my_fun_priv(atom, integer) :: boolean" } - ] = expand('my_fun_pr', env) + ] = expand(~c"my_fun_pr", env) assert [ %{name: "my_fun_pub", origin: "MyModule", type: :function} - ] = expand('my_fun_pu', env) + ] = expand(~c"my_fun_pu", env) assert [ %{name: "my_macro_priv", origin: "MyModule", type: :macro} - ] = expand('my_macro_pr', env) + ] = expand(~c"my_macro_pr", env) assert [ %{name: "my_macro_pub", origin: "MyModule", type: :macro} - ] = expand('my_macro_pu', env) + ] = expand(~c"my_macro_pu", env) assert [ %{name: "my_guard_priv", origin: "MyModule", type: :macro} - ] = expand('my_guard_pr', env) + ] = expand(~c"my_guard_pr", env) assert [ %{name: "my_guard_pub", origin: "MyModule", type: :macro} - ] = expand('my_guard_pu', env) + ] = expand(~c"my_guard_pu", env) assert [ %{name: "my_delegated", origin: "MyModule", type: :function} - ] = expand('my_de', env) + ] = expand(~c"my_de", env) end test "complete remote funs from imported module" do @@ -1249,7 +1251,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do assert [ %{name: "my_fun_other_pub", origin: "OtherModule", needed_import: nil} - ] = expand('my_f', env) + ] = expand(~c"my_f", env) end test "complete remote funs from imported module - needed import" do @@ -1282,7 +1284,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do origin: "OtherModule", needed_import: {"OtherModule", {"my_fun_other_pub", 2}} } - ] = expand('my_f', env) + ] = expand(~c"my_f", env) end test "complete remote funs" do @@ -1305,7 +1307,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do assert [ %{name: "my_fun_other_pub", origin: "Some.OtherModule"} - ] = expand('Some.OtherModule.my_f', env) + ] = expand(~c"Some.OtherModule.my_f", env) end test "complete remote funs from aliased module" do @@ -1329,7 +1331,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do assert [ %{name: "my_fun_other_pub", origin: "Some.OtherModule"} - ] = expand('S.my_f', env) + ] = expand(~c"S.my_f", env) end test "complete remote funs from injected module" do @@ -1378,21 +1380,21 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do assert [ %{name: "my_fun_other_pub", origin: "Some.OtherModule"} - ] = expand('@get_module.my_f', env) + ] = expand(~c"@get_module.my_f", env) assert [ %{name: "my_fun_other_pub", origin: "Some.OtherModule"} - ] = expand('@compile_module.my_f', env) + ] = expand(~c"@compile_module.my_f", env) Application.put_env(:elixir_sense, :other_attribute, Some.OtherModule) assert [ %{name: "my_fun_other_pub", origin: "Some.OtherModule"} - ] = expand('@fetch_module.my_f', env) + ] = expand(~c"@fetch_module.my_f", env) assert [ %{name: "my_fun_other_pub", origin: "Some.OtherModule"} - ] = expand('@compile_bang_module.my_f', env) + ] = expand(~c"@compile_bang_module.my_f", env) after Application.delete_env(:elixir_sense, :other_attribute) end @@ -1406,13 +1408,13 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } } - assert [%{name: "Some", full_name: "Some", type: :module}] = expand('Som', env) + assert [%{name: "Some", full_name: "Some", type: :module}] = expand(~c"Som", env) assert [%{name: "OtherModule", full_name: "Some.OtherModule", type: :module}] = - expand('Some.', env) + expand(~c"Some.", env) assert [%{name: "MyAlias", full_name: "Some.OtherModule.Nested", type: :module}] = - expand('MyA', env) + expand(~c"MyA", env) end test "alias rules" do @@ -1436,10 +1438,10 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "", summary: "" } - ] = expand('Keyword.valu', env) + ] = expand(~c"Keyword.valu", env) assert [%{name: "values", type: :function, arity: 1, origin: "Keyword"}] = - expand('Elixir.Keyword.valu', env) + expand(~c"Elixir.Keyword.valu", env) end defmodule MyStruct do @@ -1447,23 +1449,25 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do end test "completion for struct names" do - assert [%{name: "MyStruct"}] = expand('%ElixirSense.Providers.Suggestion.CompleteTest.MyStr') - assert entries = expand('%') + assert [%{name: "MyStruct"}] = + expand(~c"%ElixirSense.Providers.Suggestion.CompleteTest.MyStr") + + assert entries = expand(~c"%") assert entries |> Enum.any?(&(&1.name == "URI")) - assert [%{name: "MyStruct"}] = expand('%ElixirSense.Providers.Suggestion.CompleteTest.') + assert [%{name: "MyStruct"}] = expand(~c"%ElixirSense.Providers.Suggestion.CompleteTest.") end @tag requires_elixir_1_14: true test "completion for struct names with __MODULE__" do - assert [%{name: "__MODULE__"}] = expand('%__MODU', %Env{scope_module: Date.Range}) - assert [%{name: "Range"}] = expand('%__MODULE__.Ra', %Env{scope_module: Date}) + assert [%{name: "__MODULE__"}] = expand(~c"%__MODU", %Env{scope_module: Date.Range}) + assert [%{name: "Range"}] = expand(~c"%__MODULE__.Ra", %Env{scope_module: Date}) end @tag requires_elixir_1_14: true test "completion for struct attributes" do assert [%{name: "@my_attr"}] = - expand('%@my', %Env{ + expand(~c"%@my", %Env{ attributes: [ %AttributeInfo{ name: :my_attr, @@ -1474,7 +1478,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do }) assert [%{name: "Range"}] = - expand('%@my_attr.R', %Env{ + expand(~c"%@my_attr.R", %Env{ attributes: [ %AttributeInfo{ name: :my_attr, @@ -1518,7 +1522,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do ] } - assert expand('struct.my', env) == + assert expand(~c"struct.my", env) == [ %{ name: "my_val", @@ -1530,7 +1534,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('struct.some_m', env) == + assert expand(~c"struct.some_m", env) == [ %{ name: "some_map", @@ -1542,7 +1546,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('struct.some_map.', env) == + assert expand(~c"struct.some_map.", env) == [ %{ name: "asdf", @@ -1554,7 +1558,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('struct.str.', env) == + assert expand(~c"struct.str.", env) == [ %{ name: "__struct__", @@ -1606,7 +1610,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('struct.str', env) == + assert expand(~c"struct.str", env) == [ %{ name: "str", @@ -1618,7 +1622,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } ] - assert expand('struct.unknown_str.', env) == + assert expand(~c"struct.unknown_str.", env) == [ %{ call?: true, @@ -1641,7 +1645,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do test "ignore invalid Elixir module literals" do defmodule :"ElixirSense.Providers.Suggestion.CompleteTest.Unicodé", do: nil - assert expand('ElixirSense.Providers.Suggestion.CompleteTest.Unicod') == [] + assert expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.Unicod") == [] after :code.purge(:"ElixirSense.Providers.Suggestion.CompleteTest.Unicodé") :code.delete(:"ElixirSense.Providers.Suggestion.CompleteTest.Unicodé") @@ -1660,24 +1664,24 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do test "complete macros and functions from not loaded modules" do assert [%{name: "test", type: :macro}] = - expand('ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.te') + expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.te") assert [%{name: "fun", type: :function}] = - expand('ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.f') + expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.f") assert [%{name: "guard", type: :macro}] = - expand('ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.g') + expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.g") assert [%{name: "delegated", type: :function}] = - expand('ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.de') + expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.de") end test "complete built in functions on non local calls" do - assert [] = expand('module_') - assert [] = expand('__in') + assert [] = expand(~c"module_") + assert [] = expand(~c"__in") - assert [] = expand('Elixir.mo') - assert [] = expand('Elixir.__in') + assert [] = expand(~c"Elixir.mo") + assert [] = expand(~c"Elixir.__in") assert [ %{ @@ -1694,7 +1698,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec module_info(:module) :: atom\n@spec module_info(:attributes | :compile) :: [{atom, term}]\n@spec module_info(:md5) :: binary\n@spec module_info(:exports | :functions | :nifs) :: [{atom, non_neg_integer}]\n@spec module_info(:native) :: boolean" } - ] = expand('ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.mo') + ] = expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.mo") assert [ %{ @@ -1703,7 +1707,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec __info__(:attributes) :: keyword()\n@spec __info__(:compile) :: [term()]\n@spec __info__(:functions) :: [{atom, non_neg_integer}]\n@spec __info__(:macros) :: [{atom, non_neg_integer}]\n@spec __info__(:md5) :: binary()\n@spec __info__(:module) :: module()" } - ] = expand('ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.__in') + ] = expand(~c"ElixirSense.Providers.Suggestion.CompleteTest.MyMacro.__in") assert [ %{ @@ -1720,9 +1724,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec module_info(:module) :: atom\n@spec module_info(:attributes | :compile) :: [{atom, term}]\n@spec module_info(:md5) :: binary\n@spec module_info(:exports | :functions | :nifs) :: [{atom, non_neg_integer}]\n@spec module_info(:native) :: boolean" } - ] = expand(':ets.module_') + ] = expand(~c":ets.module_") - assert [] = expand(':ets.__in') + assert [] = expand(~c":ets.__in") env = %Env{ scope_module: MyModule, @@ -1737,8 +1741,8 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do } } - assert [] = expand('module_', env) - assert [] = expand('__in', env) + assert [] = expand(~c"module_", env) + assert [] = expand(~c"__in", env) assert [ %{ @@ -1755,7 +1759,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec module_info(:module) :: atom\n@spec module_info(:attributes | :compile) :: [{atom, term}]\n@spec module_info(:md5) :: binary\n@spec module_info(:exports | :functions | :nifs) :: [{atom, non_neg_integer}]\n@spec module_info(:native) :: boolean" } - ] = expand('MyModule.mo', env) + ] = expand(~c"MyModule.mo", env) assert [ %{ @@ -1764,11 +1768,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec __info__(:attributes) :: keyword()\n@spec __info__(:compile) :: [term()]\n@spec __info__(:functions) :: [{atom, non_neg_integer}]\n@spec __info__(:macros) :: [{atom, non_neg_integer}]\n@spec __info__(:md5) :: binary()\n@spec __info__(:module) :: module()" } - ] = expand('MyModule.__in', env) + ] = expand(~c"MyModule.__in", env) end test "complete build in behaviour functions" do - assert [] = expand('Elixir.beh') + assert [] = expand(~c"Elixir.beh") assert [ %{ @@ -1778,7 +1782,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec behaviour_info(:callbacks | :optional_callbacks) :: [{atom, non_neg_integer}]" } - ] = expand(':gen_server.beh') + ] = expand(~c":gen_server.beh") assert [ %{ @@ -1788,11 +1792,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec behaviour_info(:callbacks | :optional_callbacks) :: [{atom, non_neg_integer}]" } - ] = expand('GenServer.beh') + ] = expand(~c"GenServer.beh") end test "complete build in protocol functions" do - assert [] = expand('Elixir.__pr') + assert [] = expand(~c"Elixir.__pr") assert [ %{ @@ -1802,9 +1806,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec __protocol__(:module) :: module\n@spec __protocol__(:functions) :: [{atom, non_neg_integer}]\n@spec __protocol__(:consolidated?) :: boolean\n@spec __protocol__(:impls) :: :not_consolidated | {:consolidated, [module]}" } - ] = expand('Enumerable.__pro') + ] = expand(~c"Enumerable.__pro") - assert [_, _] = expand('Enumerable.imp') + assert [_, _] = expand(~c"Enumerable.imp") assert [ %{ @@ -1813,11 +1817,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do arity: 1, spec: "@spec impl_for!(term) :: atom" } - ] = expand('Enumerable.impl_for!') + ] = expand(~c"Enumerable.impl_for!") end test "complete build in protocol implementation functions" do - assert [] = expand('Elixir.__im') + assert [] = expand(~c"Elixir.__im") assert [ %{ @@ -1826,11 +1830,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do arity: 1, spec: "@spec __impl__(:for | :target | :protocol) :: module" } - ] = expand('Enumerable.List.__im') + ] = expand(~c"Enumerable.List.__im") end test "complete build in struct functions" do - assert [] = expand('Elixir.__str') + assert [] = expand(~c"Elixir.__str") assert [ %{ @@ -1847,11 +1851,11 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec __struct__(keyword) :: %{required(:__struct__) => module, optional(any) => any}" } - ] = expand('ElixirSenseExample.ModuleWithStruct.__str') + ] = expand(~c"ElixirSenseExample.ModuleWithStruct.__str") end test "complete build in exception functions" do - assert [] = expand('Elixir.mes') + assert [] = expand(~c"Elixir.mes") assert [ %{ @@ -1860,9 +1864,9 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do arity: 1, spec: "@spec message(Exception.t()) :: String.t()" } - ] = expand('ArgumentError.mes') + ] = expand(~c"ArgumentError.mes") - assert [] = expand('Elixir.exce') + assert [] = expand(~c"Elixir.exce") assert [ %{ @@ -1871,13 +1875,13 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do arity: 1, spec: "@spec exception(term) :: Exception.t()" } - ] = expand('ArgumentError.exce') + ] = expand(~c"ArgumentError.exce") - assert [] = expand('Elixir.bla') + assert [] = expand(~c"Elixir.bla") assert [ %{name: "blame", type: :function, arity: 2} - ] = expand('ArgumentError.bla') + ] = expand(~c"ArgumentError.bla") end @tag requires_otp_23: true @@ -1902,7 +1906,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do summary: "", type: :function } - ] = expand(':erlang.or') + ] = expand(~c":erlang.or") assert [ %{ @@ -1925,7 +1929,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do }, %{arity: 2, name: "append", origin: ":erlang"}, %{arity: 2, name: "append_element", origin: ":erlang"} - ] = expand(':erlang.and') + ] = expand(~c":erlang.and") end test "provide doc and specs for erlang functions" do @@ -1937,7 +1941,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do spec: "@spec whereis(regName) :: pid | port | :undefined when regName: atom", type: :function } - ] = expand(':erlang.where') + ] = expand(~c":erlang.where") assert [ %{ @@ -1958,7 +1962,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do origin: ":erlang", summary: summary2 } - ] = expand(':erlang.cancel_time') + ] = expand(~c":erlang.cancel_time") if ExUnitConfig.erlang_eep48_supported() do assert "Cancels a timer\\." <> _ = summary1 @@ -1967,7 +1971,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do end test "complete after ! operator" do - assert [%{name: "is_binary"}] = expand('!is_bina') + assert [%{name: "is_binary"}] = expand(~c"!is_bina") end test "correctly find subtype and doc for modules that have submodule" do @@ -1980,33 +1984,33 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do subtype: nil, summary: "This module contains functions to manipulate files." } - ] = expand('Fi') |> Enum.filter(&(&1.name == "File")) + ] = expand(~c"Fi") |> Enum.filter(&(&1.name == "File")) end test "complete only struct modules after %" do - assert list = expand('%') + assert list = expand(~c"%") refute Enum.any?(list, &(&1.type != :module)) assert Enum.any?(list, &(&1.name == "ArithmeticError")) assert Enum.any?(list, &(&1.name == "URI")) refute Enum.any?(list, &(&1.name == "File")) refute Enum.any?(list, &(&1.subtype not in [:struct, :exception])) - assert [_ | _] = expand('%Fi') - assert list = expand('%File.') + assert [_ | _] = expand(~c"%Fi") + assert list = expand(~c"%File.") assert Enum.any?(list, &(&1.name == "CopyError")) refute Enum.any?(list, &(&1.type != :module)) refute Enum.any?(list, &(&1.subtype not in [:struct, :exception])) end test "complete modules and local funs after &" do - assert list = expand('&') + assert list = expand(~c"&") assert Enum.any?(list, &(&1.type == :module)) assert Enum.any?(list, &(&1.type == :function)) refute Enum.any?(list, &(&1.type not in [:function, :module, :macro])) end test "complete Kernel.SpecialForms macros with fixed argument list" do - assert [%{args_list: ["term"]}] = expand('Kernel.SpecialForms.fn') + assert [%{args_list: ["term"]}] = expand(~c"Kernel.SpecialForms.fn") end test "macros from not required modules should add needed_require" do @@ -2020,7 +2024,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do visibility: :public }, _ - ] = expand('Logger.inf') + ] = expand(~c"Logger.inf") assert [ %{ @@ -2032,7 +2036,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do visibility: :public }, _ - ] = expand('Logger.inf', %Env{requires: [Logger]}) + ] = expand(~c"Logger.inf", %Env{requires: [Logger]}) end test "macros from not required metadata modules should add needed_require" do @@ -2056,7 +2060,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do needed_require: "MyModule", visibility: :public } - ] = expand('MyModule.inf', %Env{requires: [], mods_funs: mod_fun}) + ] = expand(~c"MyModule.inf", %Env{requires: [], mods_funs: mod_fun}) assert [ %{ @@ -2067,7 +2071,7 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do needed_require: nil, visibility: :public } - ] = expand('MyModule.inf', %Env{requires: [MyModule], mods_funs: mod_fun}) + ] = expand(~c"MyModule.inf", %Env{requires: [MyModule], mods_funs: mod_fun}) end test "macros from Kernel.SpecialForms should not add needed_require" do @@ -2081,6 +2085,6 @@ defmodule ElixirSense.Providers.Suggestion.CompleteTest do visibility: :public }, _ - ] = expand('unquote', %Env{requires: []}) + ] = expand(~c"unquote", %Env{requires: []}) end end diff --git a/test/elixir_sense/signature_test.exs b/test/elixir_sense/signature_test.exs index e51a9081..5cf96205 100644 --- a/test/elixir_sense/signature_test.exs +++ b/test/elixir_sense/signature_test.exs @@ -144,7 +144,7 @@ defmodule ElixirSense.SignatureTest do %{ documentation: "", name: "some_type_doc_false", - params: '', + params: ~c"", spec: "@type some_type_doc_false :: integer" } ] diff --git a/test/elixir_sense/suggestions_test.exs b/test/elixir_sense/suggestions_test.exs index 26a9b147..8714cc54 100644 --- a/test/elixir_sense/suggestions_test.exs +++ b/test/elixir_sense/suggestions_test.exs @@ -1585,8 +1585,8 @@ defmodule ElixirSense.SuggestionsTest do ElixirSense.suggestions(buffer, 6, 4) |> Enum.filter(fn s -> s.type == :attribute end) - assert Enum.any?(list, & &1.name == "@impl") - assert Enum.any?(list, & &1.name == "@spec") + assert Enum.any?(list, &(&1.name == "@impl")) + assert Enum.any?(list, &(&1.name == "@spec")) end test "lists doc snippets in module body" do From ad5074ef6a22c2635d0a15fb8180b7d8f443a3ba Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 6 Jul 2023 08:30:40 +0200 Subject: [PATCH 14/16] add tests fix more edge cases --- lib/elixir_sense.ex | 2 +- lib/elixir_sense/core/metadata.ex | 66 ++++---- test/elixir_sense/core/metadata_test.exs | 200 ++++++++++++++++++++++- 3 files changed, 229 insertions(+), 39 deletions(-) diff --git a/lib/elixir_sense.ex b/lib/elixir_sense.ex index 09ea699f..5f7a9cfe 100644 --- a/lib/elixir_sense.ex +++ b/lib/elixir_sense.ex @@ -231,7 +231,7 @@ defmodule ElixirSense do cursor_context = %{ text_before: text_before, text_after: text_after, - at_module_body?: Metadata.at_module_body?(buffer_file_metadata, env) + at_module_body?: Metadata.at_module_body?(env) } Suggestion.find(hint, env, buffer_file_metadata, cursor_context, module_store, opts) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 40a70e6f..31ab37ce 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -48,7 +48,7 @@ defmodule ElixirSense.Core.Metadata do Enum.to_list(metadata.specs) ++ Enum.to_list(metadata.mods_funs_to_positions) - closest_scope = + closest_scopes = all_scopes |> Enum.map(fn {{_, fun, nil}, _} when fun != nil -> @@ -62,12 +62,12 @@ defmodule ElixirSense.Core.Metadata do nil {{begin_line, begin_column}, {end_line, end_column}, _} - when (line > begin_line or (line == begin_line and column > begin_column)) and - (line < end_line or (line == end_line and column < end_column)) -> + when (line > begin_line or (line == begin_line and column >= begin_column)) and + (line < end_line or (line == end_line and column <= end_column)) -> {{begin_line, begin_column}, {end_line, end_column}} {{begin_line, begin_column}, nil, _} - when line > begin_line or (line == begin_line and column > begin_column) -> + when line > begin_line or (line == begin_line and column >= begin_column) -> case find_closest_ending(all_scopes, {begin_line, begin_column}) do nil -> {{begin_line, begin_column}, nil} @@ -89,42 +89,48 @@ defmodule ElixirSense.Core.Metadata do end end) |> Enum.filter(&(&1 != nil)) - |> Enum.max_by( + |> Enum.sort_by( fn {_key, _type, {begin_position, _end_position}} -> begin_position end, - fn -> nil end + :desc ) # |> dbg() - case closest_scope do - {key, type, {{begin_line, _begin_column}, _}} -> + case closest_scopes do + [_ | _] = scopes -> metadata.lines_to_env - |> Enum.filter(fn - {metadata_line, env} when metadata_line >= begin_line and metadata_line <= line -> - case {key, type} do - {{module, nil, nil}, _} -> - module in env.module_variants and is_atom(env.scope) - - {{module, fun, arity}, State.ModFunInfo} -> - module in env.module_variants and env.scope == {fun, arity} - - {{module, fun, arity}, type} when type in [State.TypeInfo, State.SpecInfo] -> - module in env.module_variants and env.scope == {:typespec, fun, arity} + |> Enum.filter(fn {metadata_line, env} -> + Enum.any?(scopes, fn {key, type, {{begin_line, _begin_column}, _}} -> + if metadata_line >= begin_line do + case {key, type} do + {{module, nil, nil}, _} -> + module in env.module_variants and is_atom(env.scope) + + {{module, fun, arity}, State.ModFunInfo} -> + module in env.module_variants and env.scope == {fun, arity} + + {{module, fun, arity}, type} when type in [State.TypeInfo, State.SpecInfo] -> + module in env.module_variants and env.scope == {:typespec, fun, arity} + end end - - _ -> - false + end) end) # |> dbg() - nil -> + [] -> metadata.lines_to_env end - |> Enum.max_by(fn {metadata_line, _env} -> metadata_line end, fn -> - {line, State.default_env()} - end) + |> Enum.max_by( + fn + {metadata_line, _env} when metadata_line <= line -> metadata_line + _ -> 0 + end, + fn -> + {line, State.default_env()} + end + ) |> elem(1) end @@ -134,7 +140,7 @@ defmodule ElixirSense.Core.Metadata do {{_, fun, nil}, _} when fun != nil -> nil - {key, %type{positions: positions, end_positions: end_positions, generated: generated}} -> + {_key, %{positions: positions, end_positions: end_positions, generated: generated}} -> Enum.zip([positions, end_positions, generated]) |> Enum.map(fn {_, _, true} -> @@ -161,9 +167,9 @@ defmodule ElixirSense.Core.Metadata do |> Enum.min(fn -> nil end) end - @spec at_module_body?(__MODULE__.t(), State.Env.t()) :: boolean() - def at_module_body?(%__MODULE__{} = metadata, env) do - is_atom(env.scope) + @spec at_module_body?(State.Env.t()) :: boolean() + def at_module_body?(env) do + is_atom(env.scope) and env.scope != Elixir end def get_position_to_insert_alias(%__MODULE__{} = metadata, {line, column}) do diff --git a/test/elixir_sense/core/metadata_test.exs b/test/elixir_sense/core/metadata_test.exs index c00cfba0..007ef7e9 100644 --- a/test/elixir_sense/core/metadata_test.exs +++ b/test/elixir_sense/core/metadata_test.exs @@ -110,33 +110,217 @@ defmodule ElixirSense.Core.MetadataTest do def go, # 16 do: :ok # 17 end + IO.puts "" """ metadata = Parser.parse_string(code, true, true, 1) env = Metadata.get_env(metadata, {1, 22}) - assert Metadata.at_module_body?(metadata, env) + assert Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {2, 20}) - refute Metadata.at_module_body?(metadata, env) + refute Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {8, 13}) - assert Metadata.at_module_body?(metadata, env) + assert Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {5, 5}) - refute Metadata.at_module_body?(metadata, env) + refute Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {11, 5}) - assert Metadata.at_module_body?(metadata, env) + assert Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {14, 18}) - refute Metadata.at_module_body?(metadata, env) + refute Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {16, 10}) - refute Metadata.at_module_body?(metadata, env) + refute Metadata.at_module_body?(env) env = Metadata.get_env(metadata, {17, 12}) - refute Metadata.at_module_body?(metadata, env) + refute Metadata.at_module_body?(env) + + env = Metadata.get_env(metadata, {19, 1}) + refute Metadata.at_module_body?(env) + end + + test "env is correct in scopes" do + code = """ + IO.puts "" + + defmodule MyModule1 do + IO.puts "" + end + + IO.puts "" + + defmodule MyModule2 do + @type a :: atom() + + defp func(1) do + IO.puts "" + end # + + IO.puts "" + + def go1, do: :ok + + def go2(a) when is_integer(a), + do: :ok + + IO.puts "" + + def go2, do: :ok # + + IO.puts "" + + @spec go31(:a) :: :ok + def go31(:a), do: :ok + def go32(:b), do: :ok + def go33(:c) do + :ok + end + + @spec some(1) :: :ok + defp some(1) do + IO.puts "" + end + + @type x :: atom() + + @type y(a) :: + atom() + + IO.puts "" + end + + defprotocol Pr do + @spec x(t) :: :ok + def x(t) + end + + defimpl Pr, for: [String, List] do + def x(t), do: :ok + end + """ + + metadata = Parser.parse_string(code, true, true, 1) + + env = Metadata.get_env(metadata, {1, 1}) + assert env.scope == Elixir + + env = Metadata.get_env(metadata, {4, 3}) + assert env.scope == :MyModule1 + + env = Metadata.get_env(metadata, {7, 1}) + assert env.scope == Elixir + + env = Metadata.get_env(metadata, {9, 1}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {10, 2}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {10, 3}) + assert env.scope == {:typespec, :a, 0} + + env = Metadata.get_env(metadata, {10, 20}) + assert env.scope == {:typespec, :a, 0} + + env = Metadata.get_env(metadata, {12, 3}) + assert env.scope == {:func, 1} + + env = Metadata.get_env(metadata, {14, 6}) + assert env.scope == {:func, 1} + + env = Metadata.get_env(metadata, {14, 7}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {16, 3}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {18, 3}) + assert env.scope == {:go1, 0} + + env = Metadata.get_env(metadata, {20, 3}) + assert env.scope == {:go2, 1} + + env = Metadata.get_env(metadata, {21, 12}) + assert env.scope == {:go2, 1} + + env = Metadata.get_env(metadata, {23, 3}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {25, 3}) + assert env.scope == {:go2, 0} + + env = Metadata.get_env(metadata, {25, 3}) + assert env.scope == {:go2, 0} + + env = Metadata.get_env(metadata, {25, 19}) + assert env.scope == {:go2, 0} + + env = Metadata.get_env(metadata, {25, 20}) + assert env.scope == {:go2, 0} + + env = Metadata.get_env(metadata, {27, 3}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {29, 3}) + assert env.scope == {:typespec, :go31, 1} + + env = Metadata.get_env(metadata, {29, 23}) + assert env.scope == {:typespec, :go31, 1} + + env = Metadata.get_env(metadata, {30, 3}) + assert env.scope == {:go31, 1} + + env = Metadata.get_env(metadata, {30, 23}) + assert env.scope == {:go31, 1} + + env = Metadata.get_env(metadata, {31, 3}) + assert env.scope == {:go32, 1} + + env = Metadata.get_env(metadata, {32, 3}) + assert env.scope == {:go33, 1} + + env = Metadata.get_env(metadata, {36, 3}) + assert env.scope == {:typespec, :some, 1} + + env = Metadata.get_env(metadata, {37, 3}) + assert env.scope == {:some, 1} + + env = Metadata.get_env(metadata, {41, 3}) + assert env.scope == {:typespec, :x, 0} + + env = Metadata.get_env(metadata, {43, 3}) + assert env.scope == {:typespec, :y, 1} + + env = Metadata.get_env(metadata, {43, 3}) + assert env.scope == {:typespec, :y, 1} + + env = Metadata.get_env(metadata, {44, 11}) + assert env.scope == {:typespec, :y, 1} + + env = Metadata.get_env(metadata, {46, 3}) + assert env.scope == :MyModule2 + + env = Metadata.get_env(metadata, {49, 1}) + assert env.scope == :Pr + + env = Metadata.get_env(metadata, {50, 3}) + assert env.scope == {:typespec, :x, 1} + + env = Metadata.get_env(metadata, {51, 3}) + assert env.scope == {:x, 1} + + env = Metadata.get_env(metadata, {51, 11}) + assert env.scope == {:x, 1} + + env = Metadata.get_env(metadata, {54, 3}) + assert env.scope == :"String(__or__)List" + + env = Metadata.get_env(metadata, {55, 3}) + assert env.scope == {:x, 1} end test "get_position_to_insert_alias when aliases exist" do From f308402fc2d326d8501042df6bbe1cea2a2cd416 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 6 Jul 2023 09:30:08 +0200 Subject: [PATCH 15/16] small improvement --- lib/elixir_sense/core/metadata.ex | 2 +- lib/elixir_sense/core/state.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 31ab37ce..07bc6494 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -106,7 +106,7 @@ defmodule ElixirSense.Core.Metadata do if metadata_line >= begin_line do case {key, type} do {{module, nil, nil}, _} -> - module in env.module_variants and is_atom(env.scope) + module in env.module_variants and is_atom(env.scope) and env.scope != Elixir {{module, fun, arity}, State.ModFunInfo} -> module in env.module_variants and env.scope == {fun, arity} diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index 4ee59633..848c28b8 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -399,7 +399,7 @@ defmodule ElixirSense.Core.State do when is_integer(line) and is_integer(column) do current_scope = hd(hd(state.scopes)) - is_module? = is_atom(current_scope) + is_module? = is_atom(current_scope) and current_scope != Elixir if is_module? do module_name = module_name(state) From 7179750489adad16280f4e9d69fec7aa9fa61972 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 6 Jul 2023 10:17:44 +0200 Subject: [PATCH 16/16] fix credo issue --- lib/elixir_sense/core/metadata_builder.ex | 45 ++++++++++++++--------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index 19e652df..3b882627 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -755,24 +755,33 @@ defmodule ElixirSense.Core.MetadataBuilder do line = Keyword.fetch!(meta_attr, :line) column = Keyword.fetch!(meta_attr, :column) - with {type, is_definition} <- - (case List.wrap(params) do - [] -> - {nil, false} - - [param] -> - {get_binding_type(state, param), true} - - _ -> - :error - end) do - state = - add_moduledoc_positions(state, [line: line, column: column], [{name, meta, params}], line) - - new_ast = {:@, meta_attr, [{name, add_no_call(meta), params}]} - pre_module_attribute(new_ast, state, {line, column}, name, type, is_definition) - else - _ -> {[], state} + binding = + case List.wrap(params) do + [] -> + {nil, false} + + [param] -> + {get_binding_type(state, param), true} + + _ -> + :error + end + + case binding do + {type, is_definition} -> + state = + add_moduledoc_positions( + state, + [line: line, column: column], + [{name, meta, params}], + line + ) + + new_ast = {:@, meta_attr, [{name, add_no_call(meta), params}]} + pre_module_attribute(new_ast, state, {line, column}, name, type, is_definition) + + _ -> + {[], state} end end