esl / gradient

Gradient is a static typechecker for Elixir
Apache License 2.0
437 stars 13 forks source link

Research Elixir Language Server integration #13

Open erszcz opened 3 years ago

erszcz commented 3 years ago

https://github.com/elixir-lsp/elixir-ls

erszcz commented 1 year ago

See https://github.com/elixir-lsp/elixir-ls/issues/770

erszcz commented 1 year ago

Some notes on hypothetical Gradient integration with ElixirLS.

ElixirLS comes with a Dialyzer integration - it's a natural starting point:

10:45:00 erszcz @ x6 : ~/work/elixir-lsp/elixir-ls (gradient *%)
$ rg -l Dialyzer apps/language_server/
apps/language_server/lib/language_server/dialyzer.ex
apps/language_server/lib/language_server/server.ex
apps/language_server/lib/language_server/dialyzer/success_typings.ex
apps/language_server/lib/language_server/dialyzer/utils.ex
apps/language_server/lib/language_server/dialyzer/manifest.ex
apps/language_server/lib/language_server/providers/code_lens.ex
apps/language_server/lib/language_server/dialyzer/analyzer.ex
apps/language_server/lib/language_server/dialyzer/supervisor.ex
apps/language_server/lib/language_server/providers/code_lens/type_spec.ex
apps/language_server/test/dialyzer_test.exs
apps/language_server/test/fixtures/umbrella_dialyzer/mix.exs
apps/language_server/test/fixtures/dialyzer/mix.exs

However, at this stage we're not that interested in the integration details, but rather in the integration points with the main language server framework. This seems to happen in apps/language_server/lib/language_server/server.ex:

$ rg Dialyzer apps/language_server/lib/language_server/server.ex
20:  alias ElixirLS.LanguageServer.{SourceFile, Build, Protocol, JsonRpc, Dialyzer, Diagnostics}
142:        {:reply, Dialyzer.suggest_contracts([abs_path]), state}
971:    Dialyzer.analyze(state.build_ref, warn_opts, dialyzer_default_format(state))
1030:      Logger.info("Dialyzer analysis is up to date")
1041:        |> Dialyzer.suggest_contracts()
1063:    Dialyzer.check_support() == :ok and build_enabled?(state) and state.dialyzer_sup != nil
1103:    case Dialyzer.check_support() do
1113:      Dialyzer.check_support() == :ok && Map.get(settings, "dialyzerEnabled", true)
1160:        {:ok, pid} = Dialyzer.Supervisor.start_link(state.project_dir)

Specifically, the most interesting point in the above results seems to be handle_build_result, where Dialyzer analysis is started. Another interesting point is handle_dialyzer_result, which shows that Dialyzer results are received asynchronously. We could follow the same pattern with Gradient integration. On the other hand, Gradient is reasonably fast on single files, so an initial prototype could just be invoked synchronously from handle_build_result and the diagnostics published together with build result diagnostics.