Skip to content

Commit eb35805

Browse files
committed
Speed up the update_stale function.
1 parent 6b70e16 commit eb35805

File tree

2 files changed

+91
-14
lines changed

2 files changed

+91
-14
lines changed

apps/language_server/lib/language_server/dialyzer.ex

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
223223
defp trigger_analyze(state), do: state
224224

225225
defp update_stale(md5, removed_files, file_changes, timestamp) do
226-
prev_paths = MapSet.new(Map.keys(md5))
226+
prev_paths = Map.keys(md5) |> MapSet.new()
227227

228228
# FIXME: Private API
229229
all_paths =
@@ -241,19 +241,32 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
241241
|> MapSet.difference(prev_paths)
242242
|> MapSet.to_list()
243243

244-
# FIXME: Private API
245-
changed = Enum.uniq(new_paths ++ Mix.Utils.extract_stale(all_paths, [timestamp]))
246-
247-
changed_contents =
248-
Task.async_stream(
249-
changed,
250-
fn file ->
251-
content = File.read!(file)
252-
{file, content, module_md5(file)}
253-
end,
254-
timeout: :infinity
255-
)
256-
|> Enum.into([])
244+
{us, {changed, changed_contents}} =
245+
:timer.tc(fn ->
246+
changed =
247+
all_paths
248+
|> extract_stale(timestamp)
249+
|> Enum.concat(new_paths)
250+
|> Enum.uniq()
251+
252+
changed_contents =
253+
Task.async_stream(
254+
changed,
255+
fn file ->
256+
content = File.read!(file)
257+
{file, content, module_md5(file)}
258+
end,
259+
timeout: :infinity
260+
)
261+
|> Enum.into([])
262+
263+
{changed, changed_contents}
264+
end)
265+
266+
JsonRpc.log_message(
267+
:info,
268+
"[ElixirLS Dialyzer] Found #{length(changed)} changed files in #{div(us, 1000)} milliseconds"
269+
)
257270

258271
file_changes =
259272
Enum.reduce(changed_contents, file_changes, fn {:ok, {file, content, hash}}, file_changes ->
@@ -269,6 +282,23 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
269282
{removed_files, file_changes}
270283
end
271284

285+
defp extract_stale(sources, timestamp) do
286+
for source <- sources,
287+
last_modified(source) > timestamp do
288+
source
289+
end
290+
end
291+
292+
defp last_modified(path) do
293+
case File.stat(path) do
294+
{:ok, %File.Stat{mtime: mtime}} ->
295+
mtime
296+
297+
{:error, _} ->
298+
{0, 0}
299+
end
300+
end
301+
272302
defp temp_file_path(root_path, file) do
273303
Path.join([root_path, ".elixir_ls/dialyzer_tmp", file])
274304
end

apps/language_server/test/dialyzer_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,53 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do
9292
end)
9393
end
9494

95+
test "only analyzes the changed files", %{server: server} do
96+
in_fixture(__DIR__, "dialyzer", fn ->
97+
file_c = SourceFile.path_to_uri(Path.absname("lib/c.ex"))
98+
99+
capture_log(fn ->
100+
root_uri = SourceFile.path_to_uri(File.cwd!())
101+
Server.receive_packet(server, initialize_req(1, root_uri, %{}))
102+
103+
Server.receive_packet(
104+
server,
105+
did_change_configuration(%{
106+
"elixirLS" => %{"dialyzerEnabled" => true, "dialyzerFormat" => "dialyxir_long"}
107+
})
108+
)
109+
110+
assert_receive %{"method" => "textDocument/publishDiagnostics"}, 20_000
111+
112+
c_text = """
113+
defmodule C do
114+
end
115+
"""
116+
117+
c_uri = SourceFile.path_to_uri("lib/c.ex")
118+
119+
# The dialyzer process checks a second back since mtime only has second
120+
# granularity, so we need to trigger one save that will set the
121+
# timestamp, and then wait a second and save again to get the actual
122+
# changed file.
123+
for _ <- 1..2 do
124+
Server.receive_packet(server, did_open(c_uri, "elixir", 1, c_text))
125+
File.write!("lib/c.ex", c_text)
126+
Server.receive_packet(server, did_save(c_uri))
127+
assert_receive publish_diagnostics_notif(^file_c, []), 20_000
128+
129+
Process.sleep(1_500)
130+
end
131+
132+
assert_receive notification("window/logMessage", %{
133+
"message" => "[ElixirLS Dialyzer] Found 1 changed files" <> _
134+
})
135+
136+
# Stop while we're still capturing logs to avoid log leakage
137+
GenServer.stop(server)
138+
end)
139+
end)
140+
end
141+
95142
test "reports dialyxir_long formatted error", %{server: server} do
96143
in_fixture(__DIR__, "dialyzer", fn ->
97144
file_a = SourceFile.path_to_uri(Path.absname("lib/a.ex"))

0 commit comments

Comments
 (0)