Skip to content

Commit

Permalink
feat(definition): aliases (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhanberg authored Jul 3, 2023
1 parent 1d3b022 commit 6156f11
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 40 deletions.
7 changes: 4 additions & 3 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ defmodule NextLS do
nil ->
nil

[] ->
nil

[{file, line, column} | _] ->
%Location{
uri: "file://#{file}",
Expand Down Expand Up @@ -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

Expand Down
34 changes: 25 additions & 9 deletions lib/next_ls/definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions lib/next_ls/runtime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]])

Expand Down
21 changes: 9 additions & 12 deletions lib/next_ls/symbol_table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand All @@ -169,6 +168,4 @@ defmodule NextLS.SymbolTable do

{:noreply, state}
end

def foo(), do: :ok
end
43 changes: 35 additions & 8 deletions priv/monkey/_next_ls_private_compiler.ex
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}},
[]
Expand All @@ -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
}},
[]
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down
23 changes: 15 additions & 8 deletions test/next_ls/symbol_table_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions test/next_ls_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

0 comments on commit 6156f11

Please sign in to comment.