From 1d3b022657b09661afc8171428bf021409c3c4a9 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Mon, 3 Jul 2023 08:09:38 -0400 Subject: [PATCH] feat(definitions): macros (#81) --- lib/next_ls/definition.ex | 3 +- priv/monkey/_next_ls_private_compiler.ex | 36 +++-- test/next_ls_test.exs | 174 ++++++++++++++++++++++- 3 files changed, 197 insertions(+), 16 deletions(-) diff --git a/lib/next_ls/definition.ex b/lib/next_ls/definition.ex index a0e8cced..0acbd2a1 100644 --- a/lib/next_ls/definition.ex +++ b/lib/next_ls/definition.ex @@ -13,7 +13,8 @@ defmodule NextLS.Definition do ] ) - :dets.traverse(dets_symbol_table, fn x -> {:continue, x} end) + # :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] -> diff --git a/priv/monkey/_next_ls_private_compiler.ex b/priv/monkey/_next_ls_private_compiler.ex index 83987553..6f8deab1 100644 --- a/priv/monkey/_next_ls_private_compiler.ex +++ b/priv/monkey/_next_ls_private_compiler.ex @@ -3,26 +3,34 @@ defmodule NextLSPrivate.Tracer do :ok end - def trace({:remote_function, meta, module, func, arity}, env) do + 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() - Process.send( - parent, - {{:tracer, :local_function}, - %{ - meta: meta, - func: func, - arity: arity, - file: env.file, - module: module - }}, - [] - ) + if type == :remote_macro && meta[:closing][:line] != meta[:line] do + # this is the case that a macro is getting expanded from inside + # another macro expansion + :noop + else + Process.send( + parent, + {{:tracer, :local_function}, + %{ + meta: meta, + func: func, + arity: arity, + file: env.file, + module: module + }}, + [] + ) + end :ok end - def trace({:local_function, meta, func, arity}, env) do + 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() Process.send( diff --git a/test/next_ls_test.exs b/test/next_ls_test.exs index ecae3fa4..dca08fb5 100644 --- a/test/next_ls_test.exs +++ b/test/next_ls_test.exs @@ -466,7 +466,7 @@ defmodule NextLSTest do end end - describe "two" do + describe "function go to definition" do setup %{cwd: cwd} do remote = Path.join(cwd, "lib/remote.ex") @@ -629,6 +629,178 @@ defmodule NextLSTest do end end + describe "macro go to definition" do + setup %{cwd: cwd} do + remote = Path.join(cwd, "lib/remote.ex") + + File.write!(remote, """ + defmodule Remote do + defmacro bang!() do + quote do + "‼️" + end + end + end + """) + + imported = Path.join(cwd, "lib/imported.ex") + + File.write!(imported, """ + defmodule Imported do + defmacro boom() do + quote do + "💣" + end + end + end + """) + + bar = Path.join(cwd, "lib/bar.ex") + + File.write!(bar, """ + defmodule Foo do + require Remote + import Imported + + defmacrop process() do + quote location: :keep do + boom() + :ok + end + end + + def run() do + Remote.bang!() + boom() + process() + end + end + """) + + [bar: bar, imported: imported, remote: remote] + end + + setup :with_lsp + + test "go to local macro definition", %{client: client, bar: bar} 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: 14, character: 6}, + textDocument: %{uri: uri} + } + }) + + assert_result 4, %{ + "range" => %{ + "start" => %{ + "line" => 4, + "character" => 0 + }, + "end" => %{ + "line" => 4, + "character" => 0 + } + }, + "uri" => ^uri + } + end + + test "go to imported macro definition", %{client: client, bar: bar, imported: imported} 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: 13, character: 5}, + textDocument: %{uri: uri} + } + }) + + uri = uri(imported) + + assert_result 4, %{ + "range" => %{ + "start" => %{ + "line" => 1, + "character" => 0 + }, + "end" => %{ + "line" => 1, + "character" => 0 + } + }, + "uri" => ^uri + } + end + + test "go to remote macro definition", %{client: client, bar: bar, remote: remote} 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: 12, character: 13}, + textDocument: %{uri: uri} + } + }) + + uri = uri(remote) + + assert_result 4, %{ + "range" => %{ + "start" => %{ + "line" => 1, + "character" => 0 + }, + "end" => %{ + "line" => 1, + "character" => 0 + } + }, + "uri" => ^uri + } + end + end + defp with_lsp(%{tmp_dir: tmp_dir}) do root_path = Path.absname(tmp_dir)