-
Notifications
You must be signed in to change notification settings - Fork 136
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
RefactorErl Diagnostics #1137
Conversation
There was a problem hiding this 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}!">> }), |
There was a problem hiding this comment.
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()])
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() -> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
apps/els_core/src/els_config.erl
Outdated
@@ -55,7 +55,8 @@ | |||
| elvis_config_path | |||
| indexing_enabled | |||
| bsp_enabled | |||
| compiler_telemetry_enabled. | |||
| compiler_telemetry_enabled | |||
| refactorerl. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
apps/els_lsp/src/els_diagnostics.erl
Outdated
@@ -64,6 +64,8 @@ available_diagnostics() -> | |||
, <<"elvis">> | |||
, <<"unused_includes">> | |||
, <<"unused_macros">> | |||
, <<"referl_unused_macros">> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 RefactorErlels_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), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 -> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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() -> |
There was a problem hiding this comment.
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
Definitely a nice addition @robertfiko , thanks! I left a few comments here and there, but from my side the main thing would be to:
Before this can be merged, we would also need to:
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!
Thank you for everything, I will add some new commits to the PR in a few days. |
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
Main changesI hope I covered everything, that we talked about, if not, let me know!
I hope covered all major changes. |
There was a problem hiding this 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.
check_node(Node) -> | ||
Response = rpc:call(Node, ri, ls, [], 10000), | ||
case Response of | ||
{{ok, _}, {error, _}} -> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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}] -> |
There was a problem hiding this comment.
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 _
).
There was a problem hiding this comment.
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(). |
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ;-)
This function clause has an importance, when a diagnostic has no result
This is due to preparing to new diagnostics
Rebasing from 'main', upstream
There was a problem hiding this 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() -> |
There was a problem hiding this comment.
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]). | |||
|
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
Thanks! |
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.