diff --git a/CHANGELOG.md b/CHANGELOG.md index 522b6b8e6..2af7f875f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ ### Unreleased +### v0.17.10: 19 November 2023 + +#### Improvements + +- Improved validation of language server configuration +- Improved validation of debugger launch configuration +- Diagnostics with no file are now emitted on `mix.exs`. Previously they were skipped +- Debugger emits better error messages when launch configuration is invalid +- Language server made more predictable on critical errors (e.g. project directory no longer existing) + +#### Fixes + +- Fixed crash when callback from docs cannot be matched with callbacks from typespecs +- Fixed invalid expansion of `Enum.fetch` in type inference engine +- Handled a few cases of invalid unicode binaries +- Fixed crash in debugger when stacktrace frame cannot be fetched +- Increased timeout on variable evaluation +- Fixed crash in debugger when inspecting an improper list +- Fixed crash in debugger when reloading test modules and `:code.delete/1` fails + ### v0.17.9: 13 November 2023 #### Improvements diff --git a/VERSION b/VERSION index a1338f8a4..52eacacfb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.17.9 +0.17.10 diff --git a/apps/language_server/lib/language_server/cli.ex b/apps/language_server/lib/language_server/cli.ex index f83ab625c..f5926b72e 100644 --- a/apps/language_server/lib/language_server/cli.ex +++ b/apps/language_server/lib/language_server/cli.ex @@ -69,6 +69,16 @@ defmodule ElixirLS.LanguageServer.CLI do Launch.unload_not_needed_apps([:nimble_parsec, :mix_task_archive_deps, :elixir_ls_debugger]) + :persistent_term.put( + :language_server_lib_dir, + ElixirLS.LanguageServer.module_info(:compile)[:source] + ) + + :persistent_term.put( + :language_server_elixir_sense_lib_dir, + ElixirSense.module_info(:compile)[:source] + ) + WireProtocol.stream_packets(&JsonRpc.receive_packet/1) end @@ -81,6 +91,8 @@ defmodule ElixirLS.LanguageServer.CLI do end defp start_language_server do + check_otp_install_complete() + case Application.ensure_all_started(:language_server, :temporary) do {:ok, _} -> :ok @@ -90,21 +102,21 @@ defmodule ElixirLS.LanguageServer.CLI do JsonRpc.show_message(:error, message) Process.sleep(5000) - raise message + System.halt(1) {:error, {:dialyzer, {~c"no such file or directory", ~c"dialyzer.app"}}} -> message = incomplete_installation_message("#dialyzer-missing") JsonRpc.show_message(:error, message) Process.sleep(5000) - raise message + System.halt(1) {:error, _} -> message = incomplete_installation_message() JsonRpc.show_message(:error, message) Process.sleep(5000) - raise message + System.halt(1) end end @@ -175,4 +187,19 @@ defmodule ElixirLS.LanguageServer.CLI do %{} ) end + + def check_otp_install_complete do + try do + :io_lib.format(~c"~p", [[1]]) + :ok + rescue + e in ErlangError -> + Logger.error(Exception.message(e)) + message = incomplete_installation_message() + + JsonRpc.show_message(:error, message) + Process.sleep(5000) + System.halt(1) + end + end end diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 448a91c59..651439acf 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -131,8 +131,6 @@ defmodule ElixirLS.LanguageServer.Server do @impl GenServer def terminate(reason, _state) do - IO.puts(:stderr, "terminate(#{inspect(reason)})") - case reason do :normal -> :ok @@ -181,6 +179,8 @@ defmodule ElixirLS.LanguageServer.Server do {:error, type, msg, send_telemetry} -> JsonRpc.respond_with_error(id, type, msg) + do_sanity_check(state) + if send_telemetry do JsonRpc.telemetry( "lsp_request_error", @@ -374,6 +374,8 @@ defmodule ElixirLS.LanguageServer.Server do error_msg = Exception.format_exit(reason) JsonRpc.respond_with_error(id, :server_error, error_msg) + do_sanity_check(state) + JsonRpc.telemetry( "lsp_request_error", %{ @@ -819,6 +821,8 @@ defmodule ElixirLS.LanguageServer.Server do {:error, type, msg, send_telemetry, state} -> JsonRpc.respond_with_error(id, type, msg) + do_sanity_check(state) + if send_telemetry do JsonRpc.telemetry( "lsp_request_error", @@ -863,6 +867,8 @@ defmodule ElixirLS.LanguageServer.Server do error_msg = Exception.format(kind, payload, stacktrace) JsonRpc.respond_with_error(id, :server_error, error_msg) + do_sanity_check(state) + JsonRpc.telemetry( "lsp_request_error", %{ @@ -1403,6 +1409,8 @@ defmodule ElixirLS.LanguageServer.Server do end defp handle_build_result(status, diagnostics, state = %__MODULE__{}) do + do_sanity_check(state) + old_diagnostics = state.build_diagnostics ++ state.dialyzer_diagnostics ++ List.flatten(Map.values(state.parser_diagnostics)) @@ -2244,4 +2252,28 @@ defmodule ElixirLS.LanguageServer.Server do {:error, diagnostic} -> [diagnostic | warning_diagnostics] end end + + defp do_sanity_check(_state) do + try do + unless function_exported?(ElixirSense, :module_info, 1) and + :persistent_term.get(:language_server_lib_dir) == + ElixirLS.LanguageServer.module_info(:compile)[:source] do + raise "sanity check failed" + end + + unless function_exported?(ElixirLS.LanguageServer, :module_info, 1) and + :persistent_term.get(:language_server_elixir_sense_lib_dir) == + ElixirSense.module_info(:compile)[:source] do + raise "sanity check failed" + end + rescue + _ -> + Logger.error("Sanity check failed. ElixirLS needs to restart.") + + unless Application.get_env(:language_server, :test_mode) do + Process.sleep(2000) + System.halt(1) + end + end + end end diff --git a/dep_versions.exs b/dep_versions.exs index 35a819479..b5baf22de 100644 --- a/dep_versions.exs +++ b/dep_versions.exs @@ -1,5 +1,5 @@ [ - elixir_sense: "85a1b96455f36d9e0eed5cb4ca8bbf56db6caa5f", + elixir_sense: "16d28f78e5702678394523c6aa17486931740402", dialyxir_vendored: "d50dcd7101c6ebd37b57b7ee4a7888d8cb634782", jason_v: "c81537e2a5e1acacb915cf339fe400357e3c2aaa", erl2ex_vendored: "073ac6b9a44282e718b6050c7b27cedf9217a12a", diff --git a/mix.lock b/mix.lock index 163f62530..43fd32463 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir_vendored": {:git, "https://github.com/elixir-lsp/dialyxir.git", "d50dcd7101c6ebd37b57b7ee4a7888d8cb634782", [ref: "d50dcd7101c6ebd37b57b7ee4a7888d8cb634782"]}, - "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "85a1b96455f36d9e0eed5cb4ca8bbf56db6caa5f", [ref: "85a1b96455f36d9e0eed5cb4ca8bbf56db6caa5f"]}, + "elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "16d28f78e5702678394523c6aa17486931740402", [ref: "16d28f78e5702678394523c6aa17486931740402"]}, "erl2ex_vendored": {:git, "https://github.com/elixir-lsp/erl2ex.git", "073ac6b9a44282e718b6050c7b27cedf9217a12a", [ref: "073ac6b9a44282e718b6050c7b27cedf9217a12a"]}, "erlex_vendored": {:git, "https://github.com/elixir-lsp/erlex.git", "82db0e82ee4896491bc26dec99f5d795f03ab9f4", [ref: "82db0e82ee4896491bc26dec99f5d795f03ab9f4"]}, "jason_v": {:git, "https://github.com/elixir-lsp/jason.git", "c81537e2a5e1acacb915cf339fe400357e3c2aaa", [ref: "c81537e2a5e1acacb915cf339fe400357e3c2aaa"]},