diff --git a/apps/common/lib/lexical/ast/analysis.ex b/apps/common/lib/lexical/ast/analysis.ex index b2796ca96..605af5174 100644 --- a/apps/common/lib/lexical/ast/analysis.ex +++ b/apps/common/lib/lexical/ast/analysis.ex @@ -200,11 +200,11 @@ defmodule Lexical.Ast.Analysis do ]} = quoted, state ) do - module = protocol_segments + module = protocol_segments ++ for_segments line = meta[:line] current_module_alias = Alias.new(module, :__MODULE__, line) for_alias = Alias.new(for_segments, :"@for", line) - protocol_alias = Alias.new(module, :"@protocol", line) + protocol_alias = Alias.new(protocol_segments, :"@protocol", line) state |> maybe_push_implicit_alias(protocol_segments, line) diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex index 534331dbb..15c233e55 100644 --- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex +++ b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex @@ -16,27 +16,34 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.Module do require Logger + @definition_mappings %{ + defmodule: :module, + defprotocol: :protocol + } + @module_definitions Map.keys(@definition_mappings) + # extract a module definition def extract( - {:defmodule, defmodule_meta, + {definition, defmodule_meta, [{:__aliases__, module_name_meta, module_name}, module_block]} = defmodule_ast, %Reducer{} = reducer - ) do + ) + when definition in @module_definitions do %Block{} = block = Reducer.current_block(reducer) case resolve_alias(reducer, module_name) do {:ok, aliased_module} -> module_position = Metadata.position(module_name_meta) - range = to_range(reducer, module_name, module_position) + detail_range = to_range(reducer, module_name, module_position) entry = Entry.block_definition( reducer.analysis.document.path, block, Subject.module(aliased_module), - :module, + @definition_mappings[definition], block_range(reducer.analysis.document, defmodule_ast), - range, + detail_range, Application.get_application(aliased_module) ) @@ -53,6 +60,41 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.Module do end end + # defimpl MyProtocol, for: MyStruct do ... + def extract( + {:defimpl, _, + [ + {:__aliases__, module_name_meta, module_name}, + _for_block, + _impl_body + ]} = defimpl_ast, + %Reducer{} = reducer + ) do + %Block{} = block = Reducer.current_block(reducer) + + case resolve_alias(reducer, module_name) do + {:ok, aliased_module} -> + module_position = Metadata.position(module_name_meta) + detail_range = to_range(reducer, module_name, module_position) + + entry = + Entry.block_definition( + reducer.analysis.document.path, + block, + Subject.module(aliased_module), + :protocol_implementation, + block_range(reducer.analysis.document, defimpl_ast), + detail_range, + Application.get_application(aliased_module) + ) + + {:ok, entry} + + _ -> + :ignored + end + end + # This matches an elixir module reference def extract({:__aliases__, metadata, maybe_module}, %Reducer{} = reducer) when is_list(maybe_module) do diff --git a/apps/remote_control/test/lexical/remote_control/analyzer_test.exs b/apps/remote_control/test/lexical/remote_control/analyzer_test.exs index d1d356f95..7ffe31f0d 100644 --- a/apps/remote_control/test/lexical/remote_control/analyzer_test.exs +++ b/apps/remote_control/test/lexical/remote_control/analyzer_test.exs @@ -29,13 +29,13 @@ defmodule Lexical.RemoteControl.AnalyzerTest do test "works with @protocol in a protocol" do {position, document} = ~q[ - defimpl MyProtocol, for: Atom do + defimpl MyProtocol, for: Atom do - def pack(atom) do - | + def pack(atom) do + | + end end - end - ] + ] |> pop_cursor(as: :document) analysis = Ast.analyze(document) @@ -60,6 +60,22 @@ defmodule Lexical.RemoteControl.AnalyzerTest do position ) end + + test "identifies the module in a protocol implementation" do + {position, document} = + ~q[ + defimpl MyProtocol, for: Atom do + + def pack(atom) do + | + end + end + ] + |> pop_cursor(as: :document) + + analysis = Ast.analyze(document) + assert {:ok, MyProtocol.Atom} == Analyzer.current_module(analysis, position) + end end describe "reanalyze_to/2" do diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/protocol_test.exs b/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/protocol_test.exs new file mode 100644 index 000000000..11361046f --- /dev/null +++ b/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/protocol_test.exs @@ -0,0 +1,63 @@ +defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ProtocolTest do + use Lexical.Test.ExtractorCase + + def index(source) do + do_index(source, &(&1.type in [:protocol, :protocol_implementation])) + end + + describe "indexing protocol definitions" do + test "works" do + {:ok, [protocol], doc} = + ~q[ + defprotocol Something do + def activate(thing, environment) + end + ] + |> index() + + assert protocol.type == :protocol + assert protocol.subtype == :definition + assert protocol.subject == Something + + expected_block = ~q[ + «defprotocol Something do + def activate(thing, environment) + end» + ]t + + assert decorate(doc, protocol.range) == "defprotocol «Something» do" + assert decorate(doc, protocol.block_range) == expected_block + end + end + + describe "indexing protocol implementations" do + test "works" do + {:ok, [protocol], doc} = + ~q[ + defimpl Something, for: Atom do + def my_impl(atom, _opts) do + to_string(atom) + end + end + ] + |> index() + + assert protocol.type == :protocol_implementation + assert protocol.subtype == :definition + assert protocol.subject == Something + + expected_block = + ~q[ + «defimpl Something, for: Atom do + def my_impl(atom, _opts) do + to_string(atom) + end + end» + ]t + |> String.trim_trailing() + + assert decorate(doc, protocol.range) == "defimpl «Something», for: Atom do" + assert decorate(doc, protocol.block_range) == expected_block + end + end +end