Skip to content

Commit

Permalink
RefactorErl Diagnostics (erlang-ls#1137)
Browse files Browse the repository at this point in the history
Add experimental support for RefactorErl diagnostics.
  • Loading branch information
robertfiko committed Apr 1, 2022
1 parent 9eed7d4 commit b87b84d
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 0 deletions.
6 changes: 6 additions & 0 deletions apps/els_core/src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
| indexing_enabled
| bsp_enabled
| compiler_telemetry_enabled
| refactorerl
| edoc_custom_tags.

-type path() :: file:filename().
Expand All @@ -77,6 +78,7 @@
, indexing_enabled => boolean()
, bsp_enabled => boolean() | auto
, compiler_telemetry_enabled => boolean()
, refactorerl => map() | 'notconfigured'
}.

%%==============================================================================
Expand Down Expand Up @@ -138,6 +140,8 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->

IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),

RefactorErl = maps:get("refactorerl", Config, notconfigured),

%% Passed by the LSP client
ok = set(root_uri , RootUri),
%% Read from the configuration file
Expand Down Expand Up @@ -180,6 +184,8 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
%% Init Options
ok = set(capabilities , Capabilities),
ok = set(indexing_enabled, IndexingEnabled),

ok = set(refactorerl, RefactorErl),
ok.

-spec start_link() -> {ok, pid()}.
Expand Down
1 change: 1 addition & 0 deletions apps/els_lsp/src/els_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ available_diagnostics() ->
, <<"unused_includes">>
, <<"unused_macros">>
, <<"unused_record_fields">>
, <<"refactorerl">>
].

-spec default_diagnostics() -> [diagnostic_id()].
Expand Down
90 changes: 90 additions & 0 deletions apps/els_lsp/src/els_refactorerl_diagnostics.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
%%==============================================================================
%% RefactorErl Diagnostics
%%==============================================================================
-module(els_refactorerl_diagnostics).

%%==============================================================================
%% Behaviours
%%==============================================================================
-behaviour(els_diagnostics).

%%==============================================================================
%% Exports
%%==============================================================================
-export([ is_default/0
, run/1
, source/0
]).

%%==============================================================================
%% Includes & Defines
%%==============================================================================
-include("els_lsp.hrl").

%%==============================================================================
%% Types
%%==============================================================================
-type refactorerl_diagnostic_alias() :: atom().
-type refactorerl_diagnostic_result() :: {range(), string()}.
%-type refactorerl_query() :: [char()].

%%==============================================================================
%% Callback Functions
%%==============================================================================

-spec is_default() -> boolean().
is_default() ->
false.

-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
case filename:extension(Uri) of
<<".erl">> ->
case els_refactorerl_utils:referl_node() of
{error, _} ->
[];
{ok, _} ->
case els_refactorerl_utils:add(Uri) of
error ->
[];
ok ->
Module = els_uri:module(Uri),
Diags = enabled_diagnostics(),
Results = els_refactorerl_utils:run_diagnostics(Diags, Module),
make_diagnostics(Results)
end
end;
_ ->
[]
end.

-spec source() -> binary().
source() ->
els_refactorerl_utils:source_name().

%%==============================================================================
%% Internal Functions
%%==============================================================================
% @doc
% Returns the enabled diagnostics by merging default and configed
-spec enabled_diagnostics() -> [refactorerl_diagnostic_alias()].
enabled_diagnostics() ->
case els_config:get(refactorerl) of
#{"diagnostics" := List} ->
[list_to_atom(Element) || Element <- List];
_ ->
[]
end.


% @doc
% Constructs the ELS diagnostic from RefactorErl result
-spec make_diagnostics([refactorerl_diagnostic_result()]) -> any().
make_diagnostics([{Range, Message} | Tail]) ->
Severity = ?DIAGNOSTIC_WARNING,
Source = source(),
Diag = els_diagnostics:make_diagnostic(Range, Message, Severity, Source),
[ Diag | make_diagnostics(Tail) ];

make_diagnostics([]) ->
[].
160 changes: 160 additions & 0 deletions apps/els_lsp/src/els_refactorerl_utils.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
%%==============================================================================
%% Erlang LS & Refactor Erl communication
%%==============================================================================
-module(els_refactorerl_utils).

%%==============================================================================
%% API
%%==============================================================================
-export([ referl_node/0
, notification/1
, notification/2
, run_diagnostics/2
, source_name/0
, add/1
]).

%%==============================================================================
%% Includes & Defines
%%==============================================================================
-include("els_lsp.hrl").

%%==============================================================================
%% API
%%==============================================================================

%% @doc
%% Returns the RefactorErl node, if it can't, it returns error and its cause.
%% It returns the node given in config, if it is alive.
%% First it runs the validation functions, the result of validation will be
%% notified to the user.
%% If the node once was validated there will be no display messages.
%%
%% The configuration can store the node and its status.
%% - Either a simple NodeString, which needs to be checked and configure
%% - {Node, Status} where boths are atoms
%% Possible statuses: validated, disconnected, disabled
%% 'disabled', when it won't try to reconnect
%% 'disconnected', when it will try to reconnect
%% - notconfigured, the node is not configured in the config file
%%
%%
%% Node can be:
%% - NodeStr
%% - {Status, Node} where both are atoms.
%% - Status can be:
%% - validated: node is running
%% - disconnected: node is not running
%% - disabled: RefactorErl is turned off for this session. T
%% his can happen after an unsuccessfull query attempt.
-spec referl_node() -> {ok, atom()}
| {error, disconnected}
| {error, disabled}
| {error, other}.
referl_node() ->
case els_config:get(refactorerl) of
#{"node" := {Node, validated}} ->
{ok, Node};

#{"node" := {Node, disconnected}} ->
connect_node({retry, Node});

#{"node" := {_Node, disabled}} ->
{error, disabled};

#{"node" := NodeStr} ->
RT = els_config_runtime:get_name_type(),
Node = els_utils:compose_node_name(NodeStr, RT),
connect_node({validate, Node});

notconfigured ->
{error, disabled};

_ ->
{error, other}
end.


%%@doc
%% Adds a module to the RefactorErl node. Using the UI router
%% Returns 'ok' if successfull
%% Returns 'error' if it fails
-spec add(uri()) -> error | ok.
add(Uri) ->
case els_refactorerl_utils:referl_node() of
{ok, Node} ->
Path = [binary_to_list(els_uri:path(Uri))],
rpc:call(Node, referl_els, add, [Path]); %% returns error | ok
_ ->
error
end.

%%@doc
%% Runs list of diagnostic aliases on refactorerl
-spec run_diagnostics(list(), atom()) -> list().
run_diagnostics(DiagnosticAliases, Module) ->
case els_refactorerl_utils:referl_node() of
{ok, Node} ->
%% returns error | ok
rpc:call(Node, referl_els, run_diagnostics, [DiagnosticAliases, Module]);
_ -> % In this case there was probably error.
[]
end.

%%@doc
%% Util for popping up notifications
-spec notification(string(), number()) -> atom().
notification(Msg, Severity) ->
Param = #{ type => Severity,
message => list_to_binary(Msg) },
els_server:send_notification(<<"window/showMessage">>, Param).

-spec notification(string()) -> atom().
notification(Msg) ->
notification(Msg, ?MESSAGE_TYPE_INFO).

%%==============================================================================
%% Internal Functions
%%==============================================================================

%%@doc
%% Checks if the given node is running RefactorErl with ELS interface
-spec is_refactorerl(atom()) -> boolean().
is_refactorerl(Node) ->
case rpc:call(Node, referl_els, ping, [], 500) of
{refactorerl_els, pong} -> true;
_ -> false
end.

%%@doc
%% Tries to connect to a node.
%% When it status is validate, the node hasn't been checked yet,
%% so it will reports the success, and failure as well,
%%
%% when retry, it won't report.
-spec connect_node({validate | retry, atom()}) -> {error, disconnected}
| atom().
connect_node({Status, Node}) ->
Config = els_config:get(refactorerl),
case {Status, is_refactorerl(Node)} of
{validate, false} ->
els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
{error, disconnected};
{retry, false} ->
els_config:set(refactorerl, Config#{"node" => {Node, disconnected}}),
{error, disconnected};
{_, true} ->
notification("RefactorErl is connected!", ?MESSAGE_TYPE_INFO),
els_config:set(refactorerl, Config#{"node" => {Node, validated}}),
{ok, Node}
end.

%%==============================================================================
%% Values
%%==============================================================================

%%@doc
%% Common soruce name for all RefactorErl based backend(s)
-spec source_name() -> binary().
source_name() ->
<<"RefactorErl">>.
Loading

0 comments on commit b87b84d

Please sign in to comment.