From f568917f41cc9b14369e3cb5d01737a2cda7e888 Mon Sep 17 00:00:00 2001 From: Matt Baker Date: Wed, 28 Feb 2018 21:41:02 -0800 Subject: [PATCH 1/7] Detect and find references across umbrella application --- .../language_server/providers/references.ex | 30 +++++++++++++++---- .../lib/language_server/server.ex | 8 ++++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/language_server/lib/language_server/providers/references.ex b/apps/language_server/lib/language_server/providers/references.ex index bb43d9de..489b107b 100644 --- a/apps/language_server/lib/language_server/providers/references.ex +++ b/apps/language_server/lib/language_server/providers/references.ex @@ -8,8 +8,8 @@ defmodule ElixirLS.LanguageServer.Providers.References do alias ElixirLS.LanguageServer.SourceFile alias ElixirSense.Core.{Metadata, Parser, Source, Introspection} - def references(text, line, character, _include_declaration) do - xref_at_cursor(text, line, character) + def references(text, line, character, project_dir, _include_declaration) do + xref_at_cursor(project_dir, text, line, character) |> Enum.filter(fn %{line: line} -> is_integer(line) end) |> Enum.map(&build_location/1) end @@ -18,14 +18,14 @@ defmodule ElixirLS.LanguageServer.Providers.References do Mix.Tasks.Xref.__info__(:functions) |> Enum.member?({:calls, 0}) end - defp xref_at_cursor(text, line, character) do + defp xref_at_cursor(project_dir, text, line, character) do env_at_cursor = line_environment(text, line) subject_at_cursor(text, line, character) |> Introspection.split_mod_fun_call() |> expand_mod_fun(env_at_cursor) |> add_arity(env_at_cursor) - |> callers() + |> callers(project_dir) end defp line_environment(text, line) do @@ -48,8 +48,26 @@ defmodule ElixirLS.LanguageServer.Providers.References do defp add_arity({mod, fun}, %{scope: {fun, arity}, module: mod}), do: {mod, fun, arity} defp add_arity({mod, fun}, _env), do: {mod, fun, nil} - def callers(nil), do: [] - def callers(mfa), do: Mix.Tasks.Xref.calls() |> Enum.filter(caller_filter(mfa)) + def callers(nil, _), do: [] + + def callers(mfa, project_dir) do + if Mix.Project.umbrella?() do + build_dir = Path.join(project_dir, ".elixir_ls/build") + + Mix.Project.apps_paths() + |> Enum.flat_map(fn {app, path} -> + Mix.Project.in_project(app, path, [build_path: build_dir], fn _ -> + Mix.Tasks.Xref.calls() + |> Enum.map(fn %{file: file} = call -> + Map.put(call, :file, Path.join(path, file)) + end) + end) + end) + else + Mix.Tasks.Xref.calls() + end + |> Enum.filter(caller_filter(mfa)) + end defp caller_filter({module, nil, nil}), do: &match?(%{callee: {^module, _, _}}, &1) defp caller_filter({module, func, nil}), do: &match?(%{callee: {^module, ^func, _}}, &1) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index c78b0325..35ba8507 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -327,7 +327,13 @@ defmodule ElixirLS.LanguageServer.Server do fun = fn -> { :ok, - References.references(state.source_files[uri].text, line, character, include_declaration) + References.references( + state.source_files[uri].text, + line, + character, + state.project_dir, + include_declaration + ) } end From 0790dd454d3a6e00c4f8ac3581bf33ec1a7dba68 Mon Sep 17 00:00:00 2001 From: Matt Baker Date: Thu, 1 Mar 2018 23:15:19 -0800 Subject: [PATCH 2/7] Improve Reference provider with feedback, add build lock --- .../lib/language_server/build.ex | 48 +++++++++++------ .../language_server/providers/references.ex | 54 ++++++++++--------- .../lib/language_server/server.ex | 17 +++--- 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index e2741da3..e5d0d179 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -8,21 +8,23 @@ defmodule ElixirLS.LanguageServer.Build do {nil, nil} else spawn_monitor(fn -> - {us, _} = - :timer.tc(fn -> - IO.puts("Compiling with Mix env #{Mix.env()}") - - case reload_project() do - {:ok, mixfile_diagnostics} -> - {status, diagnostics} = compile() - Server.build_finished(parent, {status, mixfile_diagnostics ++ diagnostics}) - - {:error, mixfile_diagnostics} -> - Server.build_finished(parent, {:error, mixfile_diagnostics}) - end - end) - - JsonRpc.log_message(:info, "Compile took #{div(us, 1000)} milliseconds") + with_build_lock(__MODULE__, fn -> + {us, _} = + :timer.tc(fn -> + IO.puts("Compiling with Mix env #{Mix.env()}") + + case reload_project() do + {:ok, mixfile_diagnostics} -> + {status, diagnostics} = compile() + Server.build_finished(parent, {status, mixfile_diagnostics ++ diagnostics}) + + {:error, mixfile_diagnostics} -> + Server.build_finished(parent, {:error, mixfile_diagnostics}) + end + end) + + JsonRpc.log_message(:info, "Compile took #{div(us, 1000)} milliseconds") + end) end) end end @@ -93,6 +95,22 @@ defmodule ElixirLS.LanguageServer.Build do } end + def with_build_lock(requester, func) do + IO.puts("#{requester} requesting lock.") + + :global.trans({:compilation_lock, requester}, fn -> + IO.puts("#{requester} acquired lock.") + + {us, result} = + :timer.tc(fn -> + func.() + end) + + IO.puts("#{requester} releasing lock after #{div(us, 1000)}ms.") + result + end) + end + defp reload_project do mixfile = Path.absname(System.get_env("MIX_EXS") || "mix.exs") diff --git a/apps/language_server/lib/language_server/providers/references.ex b/apps/language_server/lib/language_server/providers/references.ex index 489b107b..c5fb47f8 100644 --- a/apps/language_server/lib/language_server/providers/references.ex +++ b/apps/language_server/lib/language_server/providers/references.ex @@ -5,27 +5,29 @@ defmodule ElixirLS.LanguageServer.Providers.References do any function or module identified at the provided location. """ - alias ElixirLS.LanguageServer.SourceFile + alias ElixirLS.LanguageServer.{SourceFile, Build} alias ElixirSense.Core.{Metadata, Parser, Source, Introspection} - def references(text, line, character, project_dir, _include_declaration) do - xref_at_cursor(project_dir, text, line, character) - |> Enum.filter(fn %{line: line} -> is_integer(line) end) - |> Enum.map(&build_location/1) + def references(text, line, character, _include_declaration) do + Build.with_build_lock(__MODULE__, fn -> + xref_at_cursor(text, line, character) + |> Enum.filter(fn %{line: line} -> is_integer(line) end) + |> Enum.map(&build_location/1) + end) end def supported? do Mix.Tasks.Xref.__info__(:functions) |> Enum.member?({:calls, 0}) end - defp xref_at_cursor(project_dir, text, line, character) do + defp xref_at_cursor(text, line, character) do env_at_cursor = line_environment(text, line) subject_at_cursor(text, line, character) |> Introspection.split_mod_fun_call() |> expand_mod_fun(env_at_cursor) |> add_arity(env_at_cursor) - |> callers(project_dir) + |> callers() end defp line_environment(text, line) do @@ -48,37 +50,41 @@ defmodule ElixirLS.LanguageServer.Providers.References do defp add_arity({mod, fun}, %{scope: {fun, arity}, module: mod}), do: {mod, fun, arity} defp add_arity({mod, fun}, _env), do: {mod, fun, nil} - def callers(nil, _), do: [] + defp callers(nil), do: [] - def callers(mfa, project_dir) do + defp callers(mfa) do if Mix.Project.umbrella?() do - build_dir = Path.join(project_dir, ".elixir_ls/build") - - Mix.Project.apps_paths() - |> Enum.flat_map(fn {app, path} -> - Mix.Project.in_project(app, path, [build_path: build_dir], fn _ -> - Mix.Tasks.Xref.calls() - |> Enum.map(fn %{file: file} = call -> - Map.put(call, :file, Path.join(path, file)) - end) - end) - end) + umbrella_calls() else Mix.Tasks.Xref.calls() end |> Enum.filter(caller_filter(mfa)) end + def umbrella_calls() do + build_dir = Path.expand(Mix.Project.config()[:build_path]) + + Mix.Project.apps_paths() + |> Enum.flat_map(fn {app, path} -> + Mix.Project.in_project(app, path, [build_path: build_dir], fn _ -> + Mix.Tasks.Xref.calls() + |> Enum.map(fn %{file: file} = call -> + Map.put(call, :file, Path.expand(file)) + end) + end) + end) + end + defp caller_filter({module, nil, nil}), do: &match?(%{callee: {^module, _, _}}, &1) defp caller_filter({module, func, nil}), do: &match?(%{callee: {^module, ^func, _}}, &1) defp caller_filter({module, func, arity}), do: &match?(%{callee: {^module, ^func, ^arity}}, &1) - defp build_location(call) do + defp build_location(%{file: file, line: line}) do %{ - "uri" => SourceFile.path_to_uri(call.file), + "uri" => SourceFile.path_to_uri(file), "range" => %{ - "start" => %{"line" => call.line - 1, "character" => 0}, - "end" => %{"line" => call.line - 1, "character" => 0} + "start" => %{"line" => line - 1, "character" => 0}, + "end" => %{"line" => line - 1, "character" => 0} } } end diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 35ba8507..36843fe2 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -325,16 +325,13 @@ defmodule ElixirLS.LanguageServer.Server do defp handle_request(references_req(_id, uri, line, character, include_declaration), state) do fun = fn -> - { - :ok, - References.references( - state.source_files[uri].text, - line, - character, - state.project_dir, - include_declaration - ) - } + {:ok, + References.references( + state.source_files[uri].text, + line, + character, + include_declaration + )} end {:async, fun, state} From 4d1da4efb005e8eb8ea54cf510b6ea7f5e8562a1 Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Fri, 2 Mar 2018 11:14:12 -0800 Subject: [PATCH 3/7] Simplify `with_build_lock` It's safer to lock using the PID of the caller, otherwise there's a risk that two processes can acquire the same lock. Also renamed the lock to __MODULE__ to decrease likelihood of a name collision somewhere (though it's unlikely). Since `with_build_lock` no longer requires the requesting module as a parameter, I removed the logging output as well. --- .../lib/language_server/build.ex | 18 +++--------------- .../language_server/providers/references.ex | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index e5d0d179..d344dfc0 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -8,7 +8,7 @@ defmodule ElixirLS.LanguageServer.Build do {nil, nil} else spawn_monitor(fn -> - with_build_lock(__MODULE__, fn -> + with_build_lock(fn -> {us, _} = :timer.tc(fn -> IO.puts("Compiling with Mix env #{Mix.env()}") @@ -95,20 +95,8 @@ defmodule ElixirLS.LanguageServer.Build do } end - def with_build_lock(requester, func) do - IO.puts("#{requester} requesting lock.") - - :global.trans({:compilation_lock, requester}, fn -> - IO.puts("#{requester} acquired lock.") - - {us, result} = - :timer.tc(fn -> - func.() - end) - - IO.puts("#{requester} releasing lock after #{div(us, 1000)}ms.") - result - end) + def with_build_lock(func) do + :global.trans({__MODULE__, self()}, func) end defp reload_project do diff --git a/apps/language_server/lib/language_server/providers/references.ex b/apps/language_server/lib/language_server/providers/references.ex index c5fb47f8..5fb34550 100644 --- a/apps/language_server/lib/language_server/providers/references.ex +++ b/apps/language_server/lib/language_server/providers/references.ex @@ -9,7 +9,7 @@ defmodule ElixirLS.LanguageServer.Providers.References do alias ElixirSense.Core.{Metadata, Parser, Source, Introspection} def references(text, line, character, _include_declaration) do - Build.with_build_lock(__MODULE__, fn -> + Build.with_build_lock(fn -> xref_at_cursor(text, line, character) |> Enum.filter(fn %{line: line} -> is_integer(line) end) |> Enum.map(&build_location/1) From 53e9f4f211c385b9b0b3f4405cdb81f9f2e1ea7a Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Fri, 2 Mar 2018 11:18:59 -0800 Subject: [PATCH 4/7] Remove function case that Dialyzer says is unneccessary --- .../language_server/lib/language_server/providers/references.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/language_server/lib/language_server/providers/references.ex b/apps/language_server/lib/language_server/providers/references.ex index 5fb34550..02ca20f7 100644 --- a/apps/language_server/lib/language_server/providers/references.ex +++ b/apps/language_server/lib/language_server/providers/references.ex @@ -50,8 +50,6 @@ defmodule ElixirLS.LanguageServer.Providers.References do defp add_arity({mod, fun}, %{scope: {fun, arity}, module: mod}), do: {mod, fun, arity} defp add_arity({mod, fun}, _env), do: {mod, fun, nil} - defp callers(nil), do: [] - defp callers(mfa) do if Mix.Project.umbrella?() do umbrella_calls() From dda197cac2311babcbaec3271b178a9f70e97c88 Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Fri, 2 Mar 2018 11:29:10 -0800 Subject: [PATCH 5/7] Reduce code duplication in server tests --- apps/language_server/test/server_test.exs | 43 ++++++++++------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index fa4e7612..1dd2f452 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -6,6 +6,20 @@ defmodule ElixirLS.LanguageServer.ServerTest do doctest(Server) + defp initialize(server) do + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + Server.receive_packet(server, notification("initialized")) + + Server.receive_packet( + server, + did_change_configuration(%{"elixirLS" => %{"dialyzerEnabled" => false}}) + ) + end + + defp root_uri do + SourceFile.path_to_uri(File.cwd!()) + end + setup do {:ok, server} = Server.start_link() {:ok, packet_capture} = PacketCapture.start_link(self()) @@ -105,16 +119,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do test "formatter", %{server: server} do in_fixture(__DIR__, "formatter", fn -> - root_uri = SourceFile.path_to_uri(File.cwd!()) - Server.receive_packet(server, initialize_req(1, root_uri, %{})) - Server.receive_packet(server, notification("initialized")) - - Server.receive_packet( - server, - did_change_configuration(%{"elixirLS" => %{"dialyzerEnabled" => false}}) - ) - - uri = Path.join([root_uri, "file.ex"]) + uri = Path.join([root_uri(), "file.ex"]) code = ~S( defmodule MyModule do def my_fn do @@ -123,6 +128,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do end ) + initialize(server) Server.receive_packet(server, did_open(uri, "elixir", 1, code)) Server.receive_packet(server, formatting_req(1, uri, %{})) @@ -168,16 +174,9 @@ defmodule ElixirLS.LanguageServer.ServerTest do test "reports build diagnostics", %{server: server} do in_fixture(__DIR__, "build_errors", fn -> - root_uri = SourceFile.path_to_uri(File.cwd!()) error_file = SourceFile.path_to_uri("lib/has_error.ex") - Server.receive_packet(server, initialize_req(1, root_uri, %{})) - Server.receive_packet(server, notification("initialized")) - - Server.receive_packet( - server, - did_change_configuration(%{"elixirLS" => %{"dialyzerEnabled" => false}}) - ) + initialize(server) assert_receive notification("textDocument/publishDiagnostics", %{ "uri" => ^error_file, @@ -196,15 +195,9 @@ defmodule ElixirLS.LanguageServer.ServerTest do test "reports error if no mixfile", %{server: server} do in_fixture(__DIR__, "no_mixfile", fn -> - root_uri = SourceFile.path_to_uri(File.cwd!()) mixfile_uri = SourceFile.path_to_uri("mix.exs") - Server.receive_packet(server, initialize_req(1, root_uri, %{})) - Server.receive_packet(server, notification("initialized")) - Server.receive_packet( - server, - did_change_configuration(%{"elixirLS" => %{"dialyzerEnabled" => false}}) - ) + initialize(server) assert_receive notification("textDocument/publishDiagnostics", %{ "uri" => ^mixfile_uri, From 525af1f267e306aef847947f1e96eb034211eba5 Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Fri, 2 Mar 2018 11:52:08 -0800 Subject: [PATCH 6/7] Add test and fixture for find-references in umbrella project --- .../fixtures/umbrella/apps/app1/lib/app1.ex | 5 ++++ .../test/fixtures/umbrella/apps/app1/mix.exs | 15 +++++++++++ .../fixtures/umbrella/apps/app2/lib/app2.ex | 5 ++++ .../test/fixtures/umbrella/apps/app2/mix.exs | 11 ++++++++ .../test/fixtures/umbrella/mix.exs | 7 +++++ apps/language_server/test/server_test.exs | 27 +++++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 apps/language_server/test/fixtures/umbrella/apps/app1/lib/app1.ex create mode 100644 apps/language_server/test/fixtures/umbrella/apps/app1/mix.exs create mode 100644 apps/language_server/test/fixtures/umbrella/apps/app2/lib/app2.ex create mode 100644 apps/language_server/test/fixtures/umbrella/apps/app2/mix.exs create mode 100644 apps/language_server/test/fixtures/umbrella/mix.exs diff --git a/apps/language_server/test/fixtures/umbrella/apps/app1/lib/app1.ex b/apps/language_server/test/fixtures/umbrella/apps/app1/lib/app1.ex new file mode 100644 index 00000000..5a7aeb00 --- /dev/null +++ b/apps/language_server/test/fixtures/umbrella/apps/app1/lib/app1.ex @@ -0,0 +1,5 @@ +defmodule App1 do + def hello() do + App2.hello() + end +end diff --git a/apps/language_server/test/fixtures/umbrella/apps/app1/mix.exs b/apps/language_server/test/fixtures/umbrella/apps/app1/mix.exs new file mode 100644 index 00000000..6be3cbf2 --- /dev/null +++ b/apps/language_server/test/fixtures/umbrella/apps/app1/mix.exs @@ -0,0 +1,15 @@ +defmodule App1.Mixfile do + use Mix.Project + + def project do + [app: :app1, version: "0.1.0", deps: deps()] + end + + def application do + [] + end + + defp deps do + [{:app2, in_umbrella: true}] + end +end diff --git a/apps/language_server/test/fixtures/umbrella/apps/app2/lib/app2.ex b/apps/language_server/test/fixtures/umbrella/apps/app2/lib/app2.ex new file mode 100644 index 00000000..b1e1150a --- /dev/null +++ b/apps/language_server/test/fixtures/umbrella/apps/app2/lib/app2.ex @@ -0,0 +1,5 @@ +defmodule App2 do + def hello do + :app2 + end +end diff --git a/apps/language_server/test/fixtures/umbrella/apps/app2/mix.exs b/apps/language_server/test/fixtures/umbrella/apps/app2/mix.exs new file mode 100644 index 00000000..9d3b8991 --- /dev/null +++ b/apps/language_server/test/fixtures/umbrella/apps/app2/mix.exs @@ -0,0 +1,11 @@ +defmodule App2.Mixfile do + use Mix.Project + + def project do + [app: :app2, version: "0.1.0"] + end + + def application do + [] + end +end diff --git a/apps/language_server/test/fixtures/umbrella/mix.exs b/apps/language_server/test/fixtures/umbrella/mix.exs new file mode 100644 index 00000000..d8410c2d --- /dev/null +++ b/apps/language_server/test/fixtures/umbrella/mix.exs @@ -0,0 +1,7 @@ +defmodule Umbrella.Mixfile do + use Mix.Project + + def project do + [apps_path: "apps"] + end +end diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 1dd2f452..3d2b1bb8 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -211,4 +211,31 @@ defmodule ElixirLS.LanguageServer.ServerTest do 5000 end) end + + test "finds references in umbrella project", %{server: server} do + in_fixture(__DIR__, "umbrella", fn -> + file_path = "apps/app2/lib/app2.ex" + file_uri = SourceFile.path_to_uri(file_path) + text = File.read!(file_path) + reference_uri = SourceFile.path_to_uri("apps/app1/lib/app1.ex") + + initialize(server) + Server.receive_packet(server, did_open(file_uri, "elixir", 1, text)) + + Server.receive_packet( + server, + references_req(4, file_uri, 1, 9, true) + ) + + assert_receive( + response(4, [ + %{ + "range" => %{"start" => %{"line" => 2}, "end" => %{"line" => 2}}, + "uri" => ^reference_uri + } + ]), + 5000 + ) + end) + end end From 891f66f6803972703486215ce5f7ffb4dfc118ae Mon Sep 17 00:00:00 2001 From: Jake Becker Date: Fri, 2 Mar 2018 12:07:39 -0800 Subject: [PATCH 7/7] Add test and fixture for find-references in non-umbrella project --- .../test/fixtures/references/lib/a.ex | 5 ++++ .../test/fixtures/references/lib/b.ex | 5 ++++ .../test/fixtures/references/mix.exs | 11 ++++++++ apps/language_server/test/server_test.exs | 27 +++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 apps/language_server/test/fixtures/references/lib/a.ex create mode 100644 apps/language_server/test/fixtures/references/lib/b.ex create mode 100644 apps/language_server/test/fixtures/references/mix.exs diff --git a/apps/language_server/test/fixtures/references/lib/a.ex b/apps/language_server/test/fixtures/references/lib/a.ex new file mode 100644 index 00000000..b4d1eeb4 --- /dev/null +++ b/apps/language_server/test/fixtures/references/lib/a.ex @@ -0,0 +1,5 @@ +defmodule A do + def fun do + B.fun() + end +end diff --git a/apps/language_server/test/fixtures/references/lib/b.ex b/apps/language_server/test/fixtures/references/lib/b.ex new file mode 100644 index 00000000..8da5d3c1 --- /dev/null +++ b/apps/language_server/test/fixtures/references/lib/b.ex @@ -0,0 +1,5 @@ +defmodule B do + def fun do + :ok + end +end diff --git a/apps/language_server/test/fixtures/references/mix.exs b/apps/language_server/test/fixtures/references/mix.exs new file mode 100644 index 00000000..55fc1b51 --- /dev/null +++ b/apps/language_server/test/fixtures/references/mix.exs @@ -0,0 +1,11 @@ +defmodule References.MixProject do + use Mix.Project + + def project do + [app: :references, version: "0.1.0"] + end + + def application do + [] + end +end diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 3d2b1bb8..692b7894 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -212,6 +212,33 @@ defmodule ElixirLS.LanguageServer.ServerTest do end) end + test "finds references in non-umbrella project", %{server: server} do + in_fixture(__DIR__, "references", fn -> + file_path = "lib/b.ex" + file_uri = SourceFile.path_to_uri(file_path) + text = File.read!(file_path) + reference_uri = SourceFile.path_to_uri("lib/a.ex") + + initialize(server) + Server.receive_packet(server, did_open(file_uri, "elixir", 1, text)) + + Server.receive_packet( + server, + references_req(4, file_uri, 1, 8, true) + ) + + assert_receive( + response(4, [ + %{ + "range" => %{"start" => %{"line" => 2}, "end" => %{"line" => 2}}, + "uri" => ^reference_uri + } + ]), + 5000 + ) + end) + end + test "finds references in umbrella project", %{server: server} do in_fixture(__DIR__, "umbrella", fn -> file_path = "apps/app2/lib/app2.ex"