From 6156f1176581e5caa7b176528e7b72942943e61c Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Mon, 3 Jul 2023 11:14:11 -0400 Subject: [PATCH] feat(definition): aliases (#83) --- lib/next_ls.ex | 7 +-- lib/next_ls/definition.ex | 34 +++++++++---- lib/next_ls/runtime.ex | 7 +++ lib/next_ls/symbol_table.ex | 21 ++++---- priv/monkey/_next_ls_private_compiler.ex | 43 +++++++++++++--- test/next_ls/symbol_table_test.exs | 23 ++++++--- test/next_ls_test.exs | 65 ++++++++++++++++++++++++ 7 files changed, 160 insertions(+), 40 deletions(-) diff --git a/lib/next_ls.ex b/lib/next_ls.ex index 24c98f67..1aa5167b 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -117,6 +117,9 @@ defmodule NextLS do nil -> nil + [] -> + nil + [{file, line, column} | _] -> %Location{ uri: "file://#{file}", @@ -372,13 +375,11 @@ defmodule NextLS do def handle_info({:tracer, payload}, lsp) do SymbolTable.put_symbols(lsp.assigns.symbol_table, payload) - GenLSP.log(lsp, "[NextLS] Updated the symbols table!") {:noreply, lsp} end - def handle_info({{:tracer, :local_function}, payload}, lsp) do + def handle_info({{:tracer, :reference}, payload}, lsp) do SymbolTable.put_reference(lsp.assigns.symbol_table, payload) - GenLSP.log(lsp, "[NextLS] Updated the reference table!") {:noreply, lsp} end diff --git a/lib/next_ls/definition.ex b/lib/next_ls/definition.ex index 0acbd2a1..bb4581ce 100644 --- a/lib/next_ls/definition.ex +++ b/lib/next_ls/definition.ex @@ -16,18 +16,34 @@ defmodule NextLS.Definition do # :dets.traverse(dets_symbol_table, fn x -> {:continue, x} end) |> dbg # :dets.traverse(dets_ref_table, fn x -> {:continue, x} end) |> dbg - case ref do - [ref] -> - :dets.select( - dets_symbol_table, + # dbg(ref) + + query = + case ref do + [%{type: :alias} = ref] -> [ - {{:_, %{line: :"$3", name: :"$2", module: :"$1", col: :"$4", file: :"$5"}}, - [{:andalso, {:==, :"$1", ref.module}, {:==, :"$2", ref.func}}], [{{:"$5", :"$3", :"$4"}}]} + {{:_, %{line: :"$3", name: :"$2", file: :"$5", module: :"$1", col: :"$4"}}, + [ + {:andalso, {:==, :"$1", ref.module}, {:==, :"$2", Macro.to_string(ref.module)}} + ], [{{:"$5", :"$3", :"$4"}}]} ] - ) - _ -> - nil + [%{type: :function} = ref] -> + [ + {{:_, %{line: :"$3", name: :"$2", file: :"$5", module: :"$1", col: :"$4"}}, + [ + {:andalso, {:==, :"$1", ref.module}, {:==, :"$2", ref.identifier}} + ], [{{:"$5", :"$3", :"$4"}}]} + ] + + _ -> + nil + end + + if query do + :dets.select(dets_symbol_table, query) + else + nil end end end diff --git a/lib/next_ls/runtime.ex b/lib/next_ls/runtime.ex index 953d0b95..0905d34c 100644 --- a/lib/next_ls/runtime.ex +++ b/lib/next_ls/runtime.ex @@ -87,6 +87,13 @@ defmodule NextLS.Runtime do |> :code.priv_dir() |> Path.join("monkey/_next_ls_private_compiler.ex") |> then(&:rpc.call(node, Code, :compile_file, [&1])) + |> tap(fn + {:badrpc, :EXIT, {error, _}} -> + send(parent, {:log, error}) + + _ -> + :ok + end) :rpc.call(node, Code, :put_compiler_option, [:parser_options, [columns: true, token_metadata: true]]) diff --git a/lib/next_ls/symbol_table.ex b/lib/next_ls/symbol_table.ex index 3bd6f82b..fbc22e86 100644 --- a/lib/next_ls/symbol_table.ex +++ b/lib/next_ls/symbol_table.ex @@ -36,17 +36,19 @@ defmodule NextLS.SymbolTable do def init(args) do path = Keyword.fetch!(args, :path) + symbol_table_name = Keyword.get(args, :symbol_table_name, :symbol_table) + reference_table_name = Keyword.get(args, :reference_table_name, :reference_table) File.mkdir_p!(path) {:ok, name} = - :dets.open_file(:symbol_table, + :dets.open_file(symbol_table_name, file: Path.join(path, "symbol_table.dets") |> String.to_charlist(), type: :duplicate_bag ) {:ok, ref_name} = - :dets.open_file(:reference_table, + :dets.open_file(reference_table_name, file: Path.join(path, "reference_table.dets") |> String.to_charlist(), type: :duplicate_bag ) @@ -91,14 +93,14 @@ defmodule NextLS.SymbolTable do def handle_cast({:put_reference, reference}, state) do %{ meta: meta, - func: func, - arity: _arity, - file: file, - module: _module + identifier: identifier, + file: file } = reference col = meta[:column] || 0 - range = {{meta[:line], col}, {meta[:line], col + String.length(to_string(func))}} + + range = + {{meta[:line], col}, {meta[:line], col + String.length(to_string(identifier) |> String.replace("Elixir.", ""))}} :dets.insert(state.reference_table, { {file, range}, @@ -150,9 +152,6 @@ defmodule NextLS.SymbolTable do end for {name, {:v1, type, _meta, clauses}} <- defs, {meta, _, _, _} <- clauses do - _ = foo() - _ = foo() - :dets.insert( state.table, {mod, @@ -169,6 +168,4 @@ defmodule NextLS.SymbolTable do {:noreply, state} end - - def foo(), do: :ok end diff --git a/priv/monkey/_next_ls_private_compiler.ex b/priv/monkey/_next_ls_private_compiler.ex index 6f8deab1..babbfb7f 100644 --- a/priv/monkey/_next_ls_private_compiler.ex +++ b/priv/monkey/_next_ls_private_compiler.ex @@ -1,12 +1,33 @@ defmodule NextLSPrivate.Tracer do - def trace(:start, env) do + def trace(:start, _env) do + :ok + end + + def trace({:alias_reference, meta, module}, env) do + parent = parent_pid() + + alias_map = Map.new(env.aliases, fn {alias, mod} -> {mod, alias} end) + + Process.send( + parent, + {{:tracer, :reference}, + %{ + meta: meta, + identifier: Map.get(alias_map, module, module), + file: env.file, + type: :alias, + module: module + }}, + [] + ) + :ok end def trace({type, meta, module, func, arity}, env) when type in [:remote_function, :remote_macro, :imported_macro] and module not in [:elixir_def, :elixir_utils, Kernel, Enum] do - parent = "NEXTLS_PARENT_PID" |> System.get_env() |> Base.decode64!() |> :erlang.binary_to_term() + parent = parent_pid() if type == :remote_macro && meta[:closing][:line] != meta[:line] do # this is the case that a macro is getting expanded from inside @@ -15,12 +36,13 @@ defmodule NextLSPrivate.Tracer do else Process.send( parent, - {{:tracer, :local_function}, + {{:tracer, :reference}, %{ meta: meta, - func: func, + identifier: func, arity: arity, file: env.file, + type: :function, module: module }}, [] @@ -31,16 +53,17 @@ defmodule NextLSPrivate.Tracer do end def trace({type, meta, func, arity}, env) when type in [:local_function, :local_macro] do - parent = "NEXTLS_PARENT_PID" |> System.get_env() |> Base.decode64!() |> :erlang.binary_to_term() + parent = parent_pid() Process.send( parent, - {{:tracer, :local_function}, + {{:tracer, :reference}, %{ meta: meta, - func: func, + identifier: func, arity: arity, file: env.file, + type: :function, module: env.module }}, [] @@ -50,7 +73,7 @@ defmodule NextLSPrivate.Tracer do end def trace({:on_module, bytecode, _}, env) do - parent = "NEXTLS_PARENT_PID" |> System.get_env() |> Base.decode64!() |> :erlang.binary_to_term() + parent = parent_pid() defs = Module.definitions_in(env.module) @@ -82,6 +105,10 @@ defmodule NextLSPrivate.Tracer do def trace(_event, _env) do :ok end + + defp parent_pid() do + "NEXTLS_PARENT_PID" |> System.get_env() |> Base.decode64!() |> :erlang.binary_to_term() + end end defmodule :_next_ls_private_compiler do diff --git a/test/next_ls/symbol_table_test.exs b/test/next_ls/symbol_table_test.exs index 65c5b71d..c252d589 100644 --- a/test/next_ls/symbol_table_test.exs +++ b/test/next_ls/symbol_table_test.exs @@ -8,19 +8,26 @@ defmodule NextLS.SymbolTableTest do File.mkdir_p!(dir) # this fails with `{:error, incompatible_arguments}` on CI a lot, and I have no idea why - pid = - try do - start_supervised!({SymbolTable, [path: dir]}) - rescue - _ -> - Process.sleep(250) - start_supervised!({SymbolTable, [path: dir]}) - end + pid = try_start_supervised({SymbolTable, [path: dir]}, 10) Process.link(pid) [pid: pid, dir: dir] end + defp try_start_supervised(spec, 0) do + start_supervised!(spec) + end + + defp try_start_supervised(spec, num) do + try do + start_supervised!(spec) + rescue + _ -> + Process.sleep(250) + try_start_supervised(spec, num - 1) + end + end + test "creates a dets table", %{dir: dir, pid: pid} do assert File.exists?(Path.join([dir, "symbol_table.dets"])) assert :sys.get_state(pid).table == :symbol_table diff --git a/test/next_ls_test.exs b/test/next_ls_test.exs index dca08fb5..08ab3f3f 100644 --- a/test/next_ls_test.exs +++ b/test/next_ls_test.exs @@ -801,6 +801,71 @@ defmodule NextLSTest do end end + describe "module go to definition" do + setup %{cwd: cwd} do + peace = Path.join(cwd, "lib/peace.ex") + + File.write!(peace, """ + defmodule MyApp.Peace do + def and_love() do + "✌️" + end + end + """) + + bar = Path.join(cwd, "lib/bar.ex") + + File.write!(bar, """ + defmodule Bar do + alias MyApp.Peace + def run() do + Peace.and_love() + end + end + """) + + [bar: bar, peace: peace] + end + + setup :with_lsp + + test "go to module definition", %{client: client, bar: bar, peace: peace} do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_notification "window/logMessage", %{"message" => "[NextLS] Runtime ready..."} + assert_notification "window/logMessage", %{"message" => "[NextLS] Compiled!"} + + uri = uri(bar) + + request(client, %{ + method: "textDocument/definition", + id: 4, + jsonrpc: "2.0", + params: %{ + position: %{line: 3, character: 5}, + textDocument: %{uri: uri} + } + }) + + uri = uri(peace) + + assert_result 4, + %{ + "range" => %{ + "start" => %{ + "line" => 0, + "character" => 0 + }, + "end" => %{ + "line" => 0, + "character" => 0 + } + }, + "uri" => ^uri + }, + 500 + end + end + defp with_lsp(%{tmp_dir: tmp_dir}) do root_path = Path.absname(tmp_dir)