Skip to content

Commit 1e815bd

Browse files
authored
Fix crash in references provider (#807)
* fixformatting * add tests for erlang function and module references * workaround elixir tracer returning invalid paths elixir-lang/elixir#12393
1 parent 980dc4e commit 1e815bd

File tree

6 files changed

+91
-34
lines changed

6 files changed

+91
-34
lines changed

apps/language_server/lib/language_server/providers/references.ex

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ defmodule ElixirLS.LanguageServer.Providers.References do
1212

1313
alias ElixirLS.LanguageServer.{SourceFile, Build}
1414
import ElixirLS.LanguageServer.Protocol
15+
require Logger
1516

1617
def references(text, uri, line, character, _include_declaration) do
1718
{line, character} = SourceFile.lsp_position_to_elixir(text, {line, character})
@@ -24,24 +25,32 @@ defmodule ElixirLS.LanguageServer.Providers.References do
2425
elixir_sense_reference
2526
|> build_reference(uri, text)
2627
end)
28+
|> Enum.filter(&(not is_nil(&1)))
2729
end)
2830
end
2931

3032
defp build_reference(ref, current_file_uri, current_file_text) do
31-
text = get_text(ref, current_file_text)
33+
case get_text(ref, current_file_text) do
34+
{:ok, text} ->
35+
{start_line, start_column} =
36+
SourceFile.elixir_position_to_lsp(text, {ref.range.start.line, ref.range.start.column})
3237

33-
{start_line, start_column} =
34-
SourceFile.elixir_position_to_lsp(text, {ref.range.start.line, ref.range.start.column})
38+
{end_line, end_column} =
39+
SourceFile.elixir_position_to_lsp(text, {ref.range.end.line, ref.range.end.column})
3540

36-
{end_line, end_column} =
37-
SourceFile.elixir_position_to_lsp(text, {ref.range.end.line, ref.range.end.column})
41+
range = range(start_line, start_column, end_line, end_column)
3842

39-
range = range(start_line, start_column, end_line, end_column)
43+
%{
44+
"range" => range,
45+
"uri" => build_uri(ref, current_file_uri)
46+
}
4047

41-
%{
42-
"range" => range,
43-
"uri" => build_uri(ref, current_file_uri)
44-
}
48+
{:error, reason} ->
49+
# workaround for elixir tracer returning invalid paths
50+
# https://github.com/elixir-lang/elixir/issues/12393
51+
Logger.warn("Unable to open reference from #{inspect(ref.uri)}: #{inspect(reason)}")
52+
nil
53+
end
4554
end
4655

4756
def build_uri(elixir_sense_ref, current_file_uri) do
@@ -57,8 +66,8 @@ defmodule ElixirLS.LanguageServer.Providers.References do
5766

5867
def get_text(elixir_sense_ref, current_file_text) do
5968
case elixir_sense_ref.uri do
60-
nil -> current_file_text
61-
path when is_binary(path) -> File.read!(path)
69+
nil -> {:ok, current_file_text}
70+
path when is_binary(path) -> File.read(path)
6271
end
6372
end
6473
end

apps/language_server/test/experimental/project_test.exs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ defmodule ElixirLS.Experimental.ProjectTest do
6262
Project.new(root_uri())
6363
root_path = SourceFile.Path.absolute_from_uri(root_uri())
6464

65-
assert_called File.cd(^root_path)
65+
assert_called(File.cd(^root_path))
6666
end
6767

6868
test "shouldn't cd to the root uri if it doesn't exist" do
@@ -86,52 +86,52 @@ defmodule ElixirLS.Experimental.ProjectTest do
8686
assert {:ok, %Project{} = project} = Project.change_mix_env(ctx.project, "dev")
8787

8888
assert project.mix_env == :dev
89-
assert_called Mix.env(:dev)
89+
assert_called(Mix.env(:dev))
9090
end
9191

9292
test "defaults to test", ctx do
9393
assert {:ok, %Project{} = project} = Project.change_mix_env(ctx.project, "")
9494

9595
assert project.mix_env == :test
96-
assert_called Mix.env(:test)
96+
assert_called(Mix.env(:test))
9797
end
9898

9999
test "defaults to test with an empty param", ctx do
100100
assert {:ok, %Project{} = project} = Project.change_mix_env(ctx.project, nil)
101101
assert project.mix_env == :test
102-
assert_called Mix.env(:test)
102+
assert_called(Mix.env(:test))
103103
end
104104

105105
test "with the same mix env has no effect ", ctx do
106106
project = %{ctx.project | mix_env: :dev}
107107

108108
assert {:ok, %Project{} = project} = Project.change_mix_env(project, "dev")
109109
assert project.mix_env == :dev
110-
refute_called Mix.env(_)
110+
refute_called(Mix.env(_))
111111
end
112112

113113
test "overriding with nil has no effect", ctx do
114114
project = %{ctx.project | mix_env: :dev}
115115

116116
assert {:ok, %Project{} = project} = Project.change_mix_env(project, nil)
117117
assert project.mix_env == :dev
118-
refute_called Mix.env(_)
118+
refute_called(Mix.env(_))
119119
end
120120

121121
test "overriding with an emppty string has no effect", ctx do
122122
project = %{ctx.project | mix_env: :dev}
123123

124124
assert {:ok, %Project{} = project} = Project.change_mix_env(project, "")
125125
assert project.mix_env == :dev
126-
refute_called Mix.env(_)
126+
refute_called(Mix.env(_))
127127
end
128128

129129
test "to a new env requires a restar", ctx do
130130
project = %{ctx.project | mix_env: :prod}
131131

132132
assert {:restart, :warning, message} = Project.change_mix_env(project, "dev")
133133
assert message =~ "Mix env change detected."
134-
refute_called Mix.env(_)
134+
refute_called(Mix.env(_))
135135
end
136136
end
137137

@@ -154,7 +154,7 @@ defmodule ElixirLS.Experimental.ProjectTest do
154154
}
155155

156156
assert project.env_variables == expected_env_vars
157-
assert_called System.put_env(^expected_env_vars)
157+
assert_called(System.put_env(^expected_env_vars))
158158
end
159159

160160
test "keeps existing env vars if they're the same as the old ones", ctx do
@@ -168,15 +168,15 @@ defmodule ElixirLS.Experimental.ProjectTest do
168168

169169
assert {:ok, %Project{} = project} = Project.change_environment_variables(project, vars)
170170
assert project.env_variables == expected_env_vars
171-
refute_called System.put_env(_)
171+
refute_called(System.put_env(_))
172172
end
173173

174174
test "rejects env variables that aren't a compatible format", ctx do
175175
vars = ["a", "b", "c"]
176176

177177
assert {:ok, %Project{} = project} = Project.change_environment_variables(ctx.project, vars)
178178
assert project.env_variables == nil
179-
refute_called System.put_env(_)
179+
refute_called(System.put_env(_))
180180
end
181181

182182
test "requires a restart if the variables have been set and are being overridden", ctx do
@@ -185,7 +185,7 @@ defmodule ElixirLS.Experimental.ProjectTest do
185185

186186
assert {:restart, :warning, message} = Project.change_environment_variables(project, vars)
187187
assert message =~ "Environment variables have changed"
188-
refute_called System.put_env(_)
188+
refute_called(System.put_env(_))
189189
end
190190
end
191191

@@ -200,35 +200,35 @@ defmodule ElixirLS.Experimental.ProjectTest do
200200
test "allows you to set the mix target if it was unset", ctx do
201201
assert {:ok, %Project{} = project} = Project.change_mix_target(ctx.project, "local")
202202
assert project.mix_target == :local
203-
assert_called Mix.target(:local)
203+
assert_called(Mix.target(:local))
204204
end
205205

206206
test "rejects nil for the new target", ctx do
207207
assert {:ok, %Project{} = project} = Project.change_mix_target(ctx.project, nil)
208208
assert project.mix_target == nil
209-
refute_called Mix.target(:local)
209+
refute_called(Mix.target(:local))
210210
end
211211

212212
test "rejects empty string for the new target", ctx do
213213
assert {:ok, %Project{} = project} = Project.change_mix_target(ctx.project, "")
214214
assert project.mix_target == nil
215-
refute_called Mix.target(:local)
215+
refute_called(Mix.target(:local))
216216
end
217217

218218
test "does nothing if the mix target is the same as the old target", ctx do
219219
project = %Project{ctx.project | mix_target: :local}
220220

221221
assert {:ok, %Project{} = project} = Project.change_mix_target(project, "local")
222222
assert project.mix_target == :local
223-
refute_called Mix.target(:local)
223+
refute_called(Mix.target(:local))
224224
end
225225

226226
test "requires a restart if it was changed after being previously set", ctx do
227227
project = %Project{ctx.project | mix_target: :local}
228228

229229
assert {:restart, :warning, message} = Project.change_mix_target(project, "docs")
230230
assert message =~ "Mix target change detected."
231-
refute_called Mix.target(_)
231+
refute_called(Mix.target(_))
232232
end
233233
end
234234

apps/language_server/test/experimental/server/configuration_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ defmodule ElixirLS.Experimental.Server.ConfigurationTest do
6767
Configuration.new(root_uri(), fixture())
6868
root_path = SourceFile.Path.absolute_from_uri(root_uri())
6969

70-
assert_called File.cd(^root_path)
70+
assert_called(File.cd(^root_path))
7171
end
7272

7373
test "shouldn't cd to the root uri if it doesn't exist" do
@@ -135,15 +135,15 @@ defmodule ElixirLS.Experimental.Server.ConfigurationTest do
135135
assert {:ok, %Configuration{} = config} = Configuration.on_change(ctx.config, change)
136136

137137
assert config.project.mix_env == :dev
138-
assert_called Mix.env(:dev)
138+
assert_called(Mix.env(:dev))
139139
end
140140

141141
test "defaults to test", ctx do
142142
change = DidChangeConfiguration.new(settings: %{})
143143
assert {:ok, %Configuration{} = config} = Configuration.on_change(ctx.config, change)
144144

145145
assert config.project.mix_env == :test
146-
assert_called Mix.env(:test)
146+
assert_called(Mix.env(:test))
147147
end
148148
end
149149

@@ -172,7 +172,7 @@ defmodule ElixirLS.Experimental.Server.ConfigurationTest do
172172
}
173173

174174
assert config.project.env_variables == expected_env_vars
175-
assert_called System.put_env(^expected_env_vars)
175+
assert_called(System.put_env(^expected_env_vars))
176176
end
177177
end
178178

@@ -189,7 +189,7 @@ defmodule ElixirLS.Experimental.Server.ConfigurationTest do
189189

190190
assert {:ok, %Configuration{} = config} = Configuration.on_change(ctx.config, change)
191191
assert config.project.mix_target == :local
192-
assert_called Mix.target(:local)
192+
assert_called(Mix.target(:local))
193193
end
194194
end
195195

apps/language_server/test/providers/references_test.exs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ defmodule ElixirLS.LanguageServer.Providers.ReferencesTest do
3232
Code.compile_file(FixtureHelpers.get_path("references_remote.ex"))
3333
Code.compile_file(FixtureHelpers.get_path("uses_macro_a.ex"))
3434
Code.compile_file(FixtureHelpers.get_path("macro_a.ex"))
35+
Code.compile_file(FixtureHelpers.get_path("references_erlang.ex"))
3536
{:ok, context}
3637
end
3738

@@ -176,4 +177,42 @@ defmodule ElixirLS.LanguageServer.Providers.ReferencesTest do
176177
}
177178
]
178179
end
180+
181+
test "finds remote references to erlang function" do
182+
file_path = FixtureHelpers.get_path("references_referenced.ex")
183+
text = File.read!(file_path)
184+
uri = SourceFile.Path.to_uri(file_path)
185+
186+
{line, char} = {31, 10}
187+
188+
ElixirLS.Test.TextLoc.annotate_assert(file_path, line, char, """
189+
:ets.new(:abc, [])
190+
^
191+
""")
192+
193+
list = References.references(text, uri, line, char, true)
194+
195+
assert length(list) == 2
196+
assert Enum.any?(list, &(&1["uri"] |> String.ends_with?("references_erlang.ex")))
197+
assert Enum.any?(list, &(&1["uri"] |> String.ends_with?("references_referenced.ex")))
198+
end
199+
200+
test "finds remote references to erlang module" do
201+
file_path = FixtureHelpers.get_path("references_referenced.ex")
202+
text = File.read!(file_path)
203+
uri = SourceFile.Path.to_uri(file_path)
204+
205+
{line, char} = {31, 6}
206+
207+
ElixirLS.Test.TextLoc.annotate_assert(file_path, line, char, """
208+
:ets.new(:abc, [])
209+
^
210+
""")
211+
212+
list = References.references(text, uri, line, char, true)
213+
214+
assert length(list) == 2
215+
assert Enum.any?(list, &(&1["uri"] |> String.ends_with?("references_erlang.ex")))
216+
assert Enum.any?(list, &(&1["uri"] |> String.ends_with?("references_referenced.ex")))
217+
end
179218
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ElixirLS.Test.ReferencesErlang do
2+
def uses_fun do
3+
:ets.new(:my_table, [])
4+
end
5+
end

apps/language_server/test/support/fixtures/references_referenced.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ defmodule ElixirLS.Test.ReferencesReferenced do
2727
def uses_attribute do
2828
@referenced_attribute
2929
end
30+
31+
def uses_erlang_fun() do
32+
:ets.new(:abc, [])
33+
end
3034
end

0 commit comments

Comments
 (0)