Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RefactorErl Diagnostics #1137

Merged
merged 111 commits into from
Apr 1, 2022
Merged

Conversation

robertfiko
Copy link
Contributor

Description

As you (@robertoaloi) asked, I cleaned the code and created this PR with implementation for unused macro and unsecure call detection with RefactorErl backed.

Copy link
Contributor

@gomoripeti gomoripeti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice initiative

Node ->
case rpc:call(Node, ri, add, [binary_to_list(els_uri:path(Uri))], ?TIME_OUT) of
{badrpc, _} ->
els_server:send_notification(<<"window/showMessage">>, #{ type => ?MESSAGE_TYPE_ERROR, message => <<"Refactor Erl node is down {badrpc}!">> }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robertoaloi would it make sense to add shortcuts to client notifications like els_utils:show_msg(info | warning | error, Fmt :: string | binary [, Args :: list()])

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but we can probably take care of that outside of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course 👍



-spec referl_node() -> atom(). % If called with no args, then try to get it from config
referl_node() ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like some common referl functionality could be moved to a library module like els_referl_util, eg referl_node/0/1, error handling of rpc call to the referl node, processing common referl responses like [{Option, Tuple, Atom, DataList}] with a fun etc

Copy link
Contributor Author

@robertfiko robertfiko Nov 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gomoripeti I just started an els_referl module for the "common parts" should be finished by the end of the week. Also cleaned out refactor erl node detection and error management will be added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It would be nice to decouple the diagnostic backend itself from a module which deals with interfacing with RefactorErl. These could be a single els_refactorerl_diagnostics.erl (which covers all, configurable, diagnostic types) and els_refactorerl.erl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, that's a great tougth. I wrote a plan about this a few comments above.

@@ -55,7 +55,8 @@
| elvis_config_path
| indexing_enabled
| bsp_enabled
| compiler_telemetry_enabled.
| compiler_telemetry_enabled
| refactorerl.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that someone else is trying a similar approach with Wrangler (at least so I've heard), I wonder if the config should be a bit more generic, as in:

external_refactoring_tool:
  name: refactorerl | wrangler

Or similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with Melinda, for this time we wan't integrate the diagnostic and analystic features of RefactorErl intp ELS. So the config file would look like something like this:

refactorerl:
  node: referl@host
  diagnostics:
    - unsecure_call
    - unused_macros

Where the user can specify which analytic and diagnostic tools they wan't to use (there will be a documentation page on ELS website, where they can find a list of features and how to use). Of course if no refactorerl entry is added to the config file, then it won't do anything.
What are your toughts on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposal looks good to me.

@@ -64,6 +64,8 @@ available_diagnostics() ->
, <<"elvis">>
, <<"unused_includes">>
, <<"unused_macros">>
, <<"referl_unused_macros">>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my mind, it could make more sense to have <<"refactorerl">> as a single diagnostics backend, instead of one per feature. This would also help removing some of the existing duplication between the two newly introduced modules. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing.
The main conception would be to have:

  • els_refactorerl_utils for the utility functions and RPC calls and others... and will define the interface with RefactorErl
  • els_refactorerl_diagnostic for the all of the diagnostics (unused, unsecure, and others ...)
    other "groups" of functionalities may be added as single modules in the future.
    What do you think?

_ ->
ModuleName = filename:rootname(filename:basename(binary_to_list(els_uri:path(Uri)))),
ReferlResult = rpc:call(Node, refusr_sq, run, [[{positions, linecol}, {output, msg}], [], "mods[name=" ++ ModuleName ++ "].funs.unsecure_calls"]),
Pois = convertToPoi(ReferlResult),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we are not using camel case for function names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, sorry I will change it.

{badrpc, _} ->
els_server:send_notification(<<"window/showMessage">>, #{ type => ?MESSAGE_TYPE_ERROR, message => <<"Refactor Erl node is down {badrpc}!">> }),
[];
error ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the scenario where a retry can actually help? Also, not sure if we want to really pop a message every second. Maybe use logs instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the user switches between buffers, then it may occur that the execution of one diagnostic may not be finished on the first module, while another module is added to RefactorErl. The adding may change/refresh RefactorErl's database, so it will refuse the adding. Thus, the retry will be neccessary.

About the messages: of course we do not wan't pop them every execution.

ModuleName = filename:rootname(filename:basename(binary_to_list(els_uri:path(Uri)))),
ReferlResult = rpc:call(Node, refusr_sq, run, [[{positions, linecol}, {output, msg}], [], "mods[name=" ++ ModuleName ++ "].funs.unsecure_calls"]),
Pois = convertToPoi(ReferlResult),
[make_diagnostic(Poi) || Poi <- Pois]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason to convert the RefactorErl output to a POI first and then to a diagnostic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it can be done in single iteration. I will implement it.



-spec referl_node() -> atom(). % If called with no args, then try to get it from config
referl_node() ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It would be nice to decouple the diagnostic backend itself from a module which deals with interfacing with RefactorErl. These could be a single els_refactorerl_diagnostics.erl (which covers all, configurable, diagnostic types) and els_refactorerl.erl

@robertoaloi
Copy link
Member

Definitely a nice addition @robertfiko , thanks! I left a few comments here and there, but from my side the main thing would be to:

  • Use a single els_refactorerl_diagnostics backend which covers all diagnostics returned by RefactorErl (which should be configurable)
  • Introduce a separate els_refactorerl module which contains all the logic to interact with the external RefactorErl node

Before this can be merged, we would also need to:

  • Add tests (probably mocking RefactorErl)
  • Add an explanation to the user on how to install/configure RefactorErl

The real challenge with RefactorErl, as previously mentioned by @alanz , will be to find a way to provide input to the tool, since this is not really supported by the LSP protocol. We may propose an extension, but that would require custom implementations for all clients. Any suggestion in that sense is more than welcome.

@robertfiko
Copy link
Contributor Author

Definitely a nice addition @robertfiko , thanks! I left a few comments here and there, but from my side the main thing would be to:

  • Use a single els_refactorerl_diagnostics backend which covers all diagnostics returned by RefactorErl (which should be configurable)
  • Introduce a separate els_refactorerl module which contains all the logic to interact with the external RefactorErl node

Before this can be merged, we would also need to:

  • Add tests (probably mocking RefactorErl)
  • Add an explanation to the user on how to install/configure RefactorErl

The real challenge with RefactorErl, as previously mentioned by @alanz , will be to find a way to provide input to the tool, since this is not really supported by the LSP protocol. We may propose an extension, but that would require custom implementations for all clients. Any suggestion in that sense is more than welcome.

Thank you for your tougths and suggesrtions @robertoaloi!

  • I will reorded and repurpose the modules as I wrote in a comment.
  • I will write a documentation page for everything that is needed for the setup. As I can see, I can also contribute to the website, so I will write it there. (and will expand the docs when more functionalites will be added)
  • I will look into how to add tests to theese functionalities
  • A done a quick search on the "input" topic, I found something in the MS LSP Doc: https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand I do not know if that can work, but for now I will clear out things related to this two features, as this will define the interface. After it I will look deeper into the topic.

Thank you for everything, I will add some new commits to the PR in a few days.

robertfiko and others added 14 commits November 26, 2021 00:36
Upgrade LS source code, to published 0.21 (LS 0.31)
This is needed to avoid confusion between disabled which should means,
that the node is disabled *on purpose of bad behavior* from notconfigured
which self-descriptive
Heavy documentation added and new notification utility function
The syntax of Node recognition is re-taught to make more sense
Fixing the issues that were described in the PR earlier
@robertfiko
Copy link
Contributor Author

Main changes

I hope I covered everything, that we talked about, if not, let me know!

  • Documented the code the get more understandable, for you, for me, for everyone
  • Merged the logic behind the two diagnostics (unsecured calls and unused macros)
  • The RefactorErl node can be configured in the config file, like this:
refactorerl:
  node: "name@host"
  • Also, the diagnostics which the user want to use can be added here, instead of burning it into the source code. Kind of like you are doing currently with diagnostics!
  • Merged the make diagnostic and convert to poi logics into one, so we don't iterate twice.

I hope covered all major changes.

Copy link
Member

@robertoaloi robertoaloi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice progress!

I have a couple of suggestions so that we can merge this one in and then iterate.

  • The extension is somewhat experimental, so it should be off by default
  • We need some basic tests for this. Those tests could be mocking the RPC
  • The intermediate POI generation seems un-necessary and could be removed
  • I would not include error handling as a start and assume the code is up and running when the configuration is present. That should simplify this PR significantly.

apps/els_lsp/src/els_refactorerl_diagnostics.erl Outdated Show resolved Hide resolved
apps/els_lsp/src/els_refactorerl_diagnostics.erl Outdated Show resolved Hide resolved
apps/els_lsp/src/els_refactorerl_diagnostics.erl Outdated Show resolved Hide resolved
apps/els_lsp/src/els_refactorerl_utils.erl Outdated Show resolved Hide resolved
check_node(Node) ->
Response = rpc:call(Node, ri, ls, [], 10000),
case Response of
{{ok, _}, {error, _}} ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of errors do we expect here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poi = els_poi:new(Range, application, Id, Name),
[ Poi | convert_to_poi(Tail) ];
_ ->
[] %TODO Robi notify
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe write a log message here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also changed, yet if you still think it would be nice to write logs somewhere, let me know

%% The RefactorErl format is how the refusr_sq:run\3 returns
convert_to_poi(ReferlResult) ->
case ReferlResult of
[{_, _, _, DataList}] ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this function takes a generic any() as an input (which is fine, since the type is defined outside of the Erlang LS codebase), it's probably wise to name these variables, to indicate what they mean (e.g. _Message vs _).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also changed a bit, as the request to the RefactorErl node was not so stable. I will describe this in a comment when I merge to this PR. But this indication is great idea, I added to the code where I think it needs clarification, if you need it more, please let me know

end
end.

-spec convert_to_poi(any()) -> poi().
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the type is defined outside of the Erlang LS code base, I would probably define an alias and add a comment about the expected type:

%% For the type definition, please refer to [LINK HERE]
%% -type refactorerl_result() :: [{..., ...}] | ...

-type refactorerl_result() :: any().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a great idea I also taught about something similar. I'll check my possibilities on that one

[{{_, {FromLine, FromCol}, {ToLine, ToCol}}, Name} | Tail] ->
Range = #{ from => {FromLine, FromCol}, to => {ToLine, ToCol} },
Id = refactorerl_poi, %"{module(), 'atom()', 'arity()''}",
Poi = els_poi:new(Range, application, Id, Name),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we generate an intermediate POI here? Why not to generate diagnostics directly from RefactorErl results?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I also merged this, it will come soon to this PR ;-)

Copy link
Member

@robertoaloi robertoaloi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of comments, but nothing blocking. If CI passes, let's get this one in!

meck:expect(els_refactorerl_diagnostics, is_default, 0, true).


unmock_refactoerl() ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: refactorerl

@@ -818,3 +857,29 @@ src_path(Module) ->

include_path(Header) ->
filename:join(["code_navigation", "include", Header]).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be moved to a separate module.

enabled_diagnostics() ->
case els_config:get(refactorerl) of
#{"diagnostics" := List} ->
[list_to_atom(Element) || Element <- List];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It would be good to avoid the list_to_atom here.

{ok, Node} ->
%% returns error | ok
rpc:call(Node, referl_els, run_diagnostics, [DiagnosticAliases, Module]);
_ -> % In this case there was probably error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to log a message here.

@robertoaloi robertoaloi merged commit b87b84d into erlang-ls:main Apr 1, 2022
@robertoaloi
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants