Skip to content

Commit 6813f21

Browse files
authored
Merge pull request #1210 from elixir-lsp/codex/modify-handle_call-to-return-error-tuple
Handle mix project cache before load
2 parents 32acb7b + 47618da commit 6813f21

File tree

5 files changed

+102
-39
lines changed

5 files changed

+102
-39
lines changed

apps/language_server/lib/language_server/mix_project_cache.ex

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@ defmodule ElixirLS.LanguageServer.MixProjectCache do
2020
GenServer.call(__MODULE__, :loaded?)
2121
end
2222

23-
@spec get() :: module | nil
23+
@spec get() :: {:ok, module | nil} | {:error, :not_loaded}
2424
def get do
2525
GenServer.call(__MODULE__, {:get, :get})
2626
end
2727

2828
@spec get!() :: module
2929
def get! do
30-
get() || raise Mix.NoProjectError, []
30+
case get() do
31+
{:ok, project} when not is_nil(project) -> project
32+
_ -> raise Mix.NoProjectError, []
33+
end
3134
end
3235

33-
@spec project_file() :: binary | nil
36+
@spec project_file() :: {:ok, binary | nil} | {:error, :not_loaded}
3437
def project_file() do
3538
GenServer.call(__MODULE__, {:get, :project_file})
3639
end
@@ -39,47 +42,47 @@ defmodule ElixirLS.LanguageServer.MixProjectCache do
3942
# @spec parent_umbrella_project_file() :: binary | nil
4043
# defdelegate parent_umbrella_project_file(), to: Mix.ProjectStack
4144

42-
@spec config() :: keyword
45+
@spec config() :: {:ok, keyword} | {:error, :not_loaded}
4346
def config do
4447
GenServer.call(__MODULE__, {:get, :config})
4548
end
4649

47-
@spec config_files() :: [Path.t()]
50+
@spec config_files() :: {:ok, [Path.t()]} | {:error, :not_loaded}
4851
def config_files do
4952
GenServer.call(__MODULE__, {:get, :config_files})
5053
end
5154

52-
@spec config_mtime() :: posix_mtime when posix_mtime: integer()
55+
@spec config_mtime() :: {:ok, posix_mtime} | {:error, :not_loaded} when posix_mtime: integer()
5356
def config_mtime do
5457
GenServer.call(__MODULE__, {:get, :config_mtime})
5558
end
5659

57-
@spec umbrella?() :: boolean
60+
@spec umbrella?() :: {:ok, boolean} | {:error, :not_loaded}
5861
def umbrella?() do
5962
GenServer.call(__MODULE__, {:get, :umbrella?})
6063
end
6164

62-
@spec apps_paths() :: %{optional(atom) => Path.t()} | nil
65+
@spec apps_paths() :: {:ok, %{optional(atom) => Path.t()} | nil} | {:error, :not_loaded}
6366
def apps_paths() do
6467
GenServer.call(__MODULE__, {:get, :apps_paths})
6568
end
6669

67-
@spec deps_path() :: Path.t()
70+
@spec deps_path() :: {:ok, Path.t()} | {:error, :not_loaded}
6871
def deps_path() do
6972
GenServer.call(__MODULE__, {:get, :deps_path})
7073
end
7174

72-
@spec deps_apps() :: [atom()]
75+
@spec deps_apps() :: {:ok, [atom()]} | {:error, :not_loaded}
7376
def deps_apps() do
7477
GenServer.call(__MODULE__, {:get, :deps_apps})
7578
end
7679

77-
@spec deps_scms() :: %{optional(atom) => Mix.SCM.t()}
80+
@spec deps_scms() :: {:ok, %{optional(atom) => Mix.SCM.t()}} | {:error, :not_loaded}
7881
def deps_scms() do
7982
GenServer.call(__MODULE__, {:get, :deps_scms})
8083
end
8184

82-
@spec deps_paths() :: %{optional(atom) => Path.t()}
85+
@spec deps_paths() :: {:ok, %{optional(atom) => Path.t()}} | {:error, :not_loaded}
8386
def deps_paths() do
8487
GenServer.call(__MODULE__, {:get, :deps_paths})
8588
end
@@ -90,24 +93,25 @@ defmodule ElixirLS.LanguageServer.MixProjectCache do
9093
# traverse_deps(opts, fn %{deps: deps} -> Enum.map(deps, & &1.app) end)
9194
# end
9295

93-
@spec build_path() :: Path.t()
96+
@spec build_path() :: {:ok, Path.t()} | {:error, :not_loaded}
9497
def build_path() do
9598
GenServer.call(__MODULE__, {:get, :build_path})
9699
end
97100

98-
@spec manifest_path() :: Path.t()
101+
@spec manifest_path() :: {:ok, Path.t()} | {:error, :not_loaded}
99102
def manifest_path() do
100103
GenServer.call(__MODULE__, {:get, :manifest_path})
101104
end
102105

103-
@spec app_path() :: Path.t()
106+
@spec app_path() :: {:ok, Path.t()} | {:error, :not_loaded}
104107
def app_path() do
105-
config = config()
108+
{:ok, config} = config()
106109

107110
config[:deps_app_path] ||
108111
cond do
109112
app = config[:app] ->
110-
Path.join([build_path(), "lib", Atom.to_string(app)])
113+
{:ok, build_path} = build_path()
114+
Path.join([build_path, "lib", Atom.to_string(app)])
111115

112116
config[:apps_path] ->
113117
raise "trying to access Mix.Project.app_path/1 for an umbrella project but umbrellas have no app"
@@ -121,9 +125,11 @@ defmodule ElixirLS.LanguageServer.MixProjectCache do
121125
end
122126
end
123127

124-
@spec compile_path() :: Path.t()
128+
@spec compile_path() :: {:ok, Path.t()} | {:error, :not_loaded}
125129
def compile_path() do
126-
Path.join(app_path(), "ebin")
130+
with {:ok, app_path} <- app_path() do
131+
{:ok, Path.join(app_path, "ebin")}
132+
end
127133
end
128134

129135
# @spec consolidation_path() :: Path.t()
@@ -168,8 +174,12 @@ defmodule ElixirLS.LanguageServer.MixProjectCache do
168174
end
169175

170176
@impl GenServer
177+
def handle_call({:get, _key}, _from, nil = state) do
178+
{:reply, {:error, :not_loaded}, state}
179+
end
180+
171181
def handle_call({:get, key}, _from, state) do
172-
{:reply, Map.fetch!(state, key), state}
182+
{:reply, {:ok, Map.fetch!(state, key)}, state}
173183
end
174184

175185
def handle_call({:store, state}, _from, _state) do

apps/language_server/lib/language_server/server.ex

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,11 +1690,13 @@ defmodule ElixirLS.LanguageServer.Server do
16901690
Map.get(state.settings || %{}, "enableTestLenses", false)
16911691

16921692
if state.mix_project? and enabled? and MixProjectCache.loaded?() do
1693+
{:ok, umbrella?} = MixProjectCache.umbrella?()
1694+
16931695
get_test_code_lenses(
16941696
state,
16951697
uri,
16961698
source_file,
1697-
MixProjectCache.umbrella?()
1699+
umbrella?
16981700
)
16991701
else
17001702
{:ok, []}
@@ -1711,16 +1713,23 @@ defmodule ElixirLS.LanguageServer.Server do
17111713
file_path = SourceFile.Path.from_uri(uri)
17121714

17131715
MixProjectCache.apps_paths()
1714-
|> Enum.find(fn {_app, app_path} -> under_app?(file_path, project_dir, app_path) end)
17151716
|> case do
1716-
nil ->
1717+
{:error, :not_loaded} ->
17171718
{:ok, []}
17181719

1719-
{app, app_path} ->
1720-
if is_test_file?(file_path, state, app, app_path) do
1721-
CodeLens.test_code_lens(parser_context, Path.join(project_dir, app_path))
1722-
else
1723-
{:ok, []}
1720+
{:ok, apps_paths} ->
1721+
apps_paths
1722+
|> Enum.find(fn {_app, app_path} -> under_app?(file_path, project_dir, app_path) end)
1723+
|> case do
1724+
nil ->
1725+
{:ok, []}
1726+
1727+
{app, app_path} ->
1728+
if is_test_file?(file_path, state, app, app_path) do
1729+
CodeLens.test_code_lens(parser_context, Path.join(project_dir, app_path))
1730+
else
1731+
{:ok, []}
1732+
end
17241733
end
17251734
end
17261735
end
@@ -1764,15 +1773,20 @@ defmodule ElixirLS.LanguageServer.Server do
17641773
end
17651774

17661775
defp is_test_file?(file_path, project_dir) do
1767-
config = MixProjectCache.config()
1768-
test_paths = config[:test_paths] || ["test"]
1769-
test_pattern = config[:test_pattern] || "*_test.exs"
1776+
case MixProjectCache.config() do
1777+
{:error, :not_loaded} ->
1778+
false
17701779

1771-
file_path = SourceFile.Path.expand(file_path, project_dir)
1780+
{:ok, config} ->
1781+
test_paths = config[:test_paths] || ["test"]
1782+
test_pattern = config[:test_pattern] || "*_test.exs"
17721783

1773-
Mix.Utils.extract_files(test_paths, test_pattern)
1774-
|> Enum.map(&SourceFile.Path.absname(&1, project_dir))
1775-
|> Enum.any?(&(&1 == file_path))
1784+
file_path = SourceFile.Path.expand(file_path, project_dir)
1785+
1786+
Mix.Utils.extract_files(test_paths, test_pattern)
1787+
|> Enum.map(&SourceFile.Path.absname(&1, project_dir))
1788+
|> Enum.any?(&(&1 == file_path))
1789+
end
17761790
end
17771791

17781792
defp under_app?(file_path, project_dir, app_path) do

apps/language_server/lib/language_server/source_file.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,16 @@ defmodule ElixirLS.LanguageServer.SourceFile do
246246

247247
if mix_project? do
248248
if MixProjectCache.loaded?() do
249+
{:ok, deps_paths} = MixProjectCache.deps_paths()
250+
{:ok, manifest_path} = MixProjectCache.manifest_path()
251+
{:ok, config_mtime} = MixProjectCache.config_mtime()
252+
{:ok, mix_project} = MixProjectCache.get()
253+
249254
opts = [
250-
deps_paths: MixProjectCache.deps_paths(),
251-
manifest_path: MixProjectCache.manifest_path(),
252-
config_mtime: MixProjectCache.config_mtime(),
253-
mix_project: MixProjectCache.get(),
255+
deps_paths: deps_paths,
256+
manifest_path: manifest_path,
257+
config_mtime: config_mtime,
258+
mix_project: mix_project,
254259
root: project_dir,
255260
plugin_loader: fn plugins ->
256261
for plugin <- plugins do
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
defmodule ElixirLS.LanguageServer.MixProjectCacheTest do
2+
use ExUnit.Case, async: true
3+
4+
alias ElixirLS.LanguageServer.MixProjectCache
5+
6+
setup do
7+
{:ok, pid} = start_supervised(MixProjectCache)
8+
%{pid: pid}
9+
end
10+
11+
test "returns not_loaded when state is nil", %{pid: pid} do
12+
assert {:error, :not_loaded} = MixProjectCache.get()
13+
assert {:error, :not_loaded} = MixProjectCache.config()
14+
assert Process.alive?(pid)
15+
end
16+
end

apps/language_server/test/server_test.exs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2253,6 +2253,24 @@ defmodule ElixirLS.LanguageServer.ServerTest do
22532253
end
22542254
end
22552255

2256+
describe "MixProjectCache not loaded" do
2257+
@tag :fixture
2258+
test "code lens request before project load returns empty list", %{server: server} do
2259+
in_fixture(__DIR__, "no_mixfile", fn ->
2260+
initialize(server, %{"enableTestLenses" => true, "dialyzerEnabled" => false})
2261+
2262+
file_path = "a.ex"
2263+
file_uri = SourceFile.Path.to_uri(file_path)
2264+
text = File.read!(file_path)
2265+
2266+
Server.receive_packet(server, did_open(file_uri, "elixir", 1, text))
2267+
Server.receive_packet(server, code_lens_req(4, file_uri))
2268+
2269+
assert_receive(%{"id" => 4, "result" => []}, 1000)
2270+
end)
2271+
end
2272+
end
2273+
22562274
defp with_new_server(packet_capture, func) do
22572275
server = start_supervised!({Server, nil})
22582276
{:ok, mix_project} = start_supervised(MixProjectCache)

0 commit comments

Comments
 (0)