Skip to content

Commit

Permalink
Add suggest code actions for undefined record and record fields (#1539)
Browse files Browse the repository at this point in the history
  • Loading branch information
plux committed Sep 18, 2024
1 parent acd7f80 commit d38dc66
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-module(undefined_record_suggest).

-record(foobar, {foobar}).

function_a(R) ->
#foo_bar{} = R,
R#foobar.foobaz.
2 changes: 2 additions & 0 deletions apps/els_lsp/src/els_code_action_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ make_code_actions(
{"undefined macro '(.*)'", fun els_code_actions:suggest_macro/4},
{"record (.*) undefined", fun els_code_actions:add_include_lib_record/4},
{"record (.*) undefined", fun els_code_actions:define_record/4},
{"record (.*) undefined", fun els_code_actions:suggest_record/4},
{"field (.*) undefined in record (.*)", fun els_code_actions:suggest_record_field/4},
{"Module name '(.*)' does not match file name '(.*)'",
fun els_code_actions:fix_module_name/4},
{"Unused macro: (.*)", fun els_code_actions:remove_macro/4},
Expand Down
52 changes: 51 additions & 1 deletion apps/els_lsp/src/els_code_actions.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
define_record/4,
add_include_lib_macro/4,
add_include_lib_record/4,
suggest_macro/4
suggest_macro/4,
suggest_record/4,
suggest_record_field/4
]).

-include("els_lsp.hrl").
Expand Down Expand Up @@ -294,6 +296,54 @@ suggest_macro(Uri, Range, _Data, [Macro]) ->
Distance > 0.8
].

-spec suggest_record(uri(), range(), binary(), [binary()]) -> [map()].
suggest_record(Uri, Range, _Data, [Record]) ->
%% Supply a quickfix to replace an unrecognized record with the most similar
%% record in scope.
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_scope:local_and_included_pois(Document, [record]),
RecordsInScope = [atom_to_binary(Id) || #{id := Id} <- POIs, is_atom(Id)],
Distances =
[{els_utils:jaro_distance(Rec, Record), Rec} || Rec <- RecordsInScope, Rec =/= Record],
[
make_edit_action(
Uri,
<<"Did you mean #", Rec/binary, "{}?">>,
?CODE_ACTION_KIND_QUICKFIX,
<<"#", Rec/binary>>,
Range
)
|| {Distance, Rec} <- lists:reverse(lists:usort(Distances)),
Distance > 0.8
].

-spec suggest_record_field(uri(), range(), binary(), [binary()]) -> [map()].
suggest_record_field(Uri, Range, _Data, [Field, Record]) ->
%% Supply a quickfix to replace an unrecognized record field with the most
%% similar record field in Record.
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_scope:local_and_included_pois(Document, [record]),
RecordId = binary_to_atom(Record, utf8),
Fields = [
atom_to_binary(F)
|| #{id := Id, data := #{field_list := Fs}} <- POIs,
F <- Fs,
Id =:= RecordId
],
Distances =
[{els_utils:jaro_distance(F, Field), F} || F <- Fields, F =/= Field],
[
make_edit_action(
Uri,
<<"Did you mean #", Record/binary, ".", F/binary, "?">>,
?CODE_ACTION_KIND_QUICKFIX,
<<F/binary>>,
Range
)
|| {Distance, F} <- lists:reverse(lists:usort(Distances)),
Distance > 0.8
].

-spec fix_module_name(uri(), range(), binary(), [binary()]) -> [map()].
fix_module_name(Uri, Range0, _Data, [ModName, FileName]) ->
{ok, Document} = els_utils:lookup_document(Uri),
Expand Down
58 changes: 57 additions & 1 deletion apps/els_lsp/test/els_code_action_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
define_macro/1,
define_macro_with_args/1,
suggest_macro/1,
undefined_record/1
undefined_record/1,
undefined_record_suggest/1
]).

%%==============================================================================
Expand Down Expand Up @@ -736,3 +737,58 @@ undefined_record(Config) ->
],
?assertEqual(Expected, Result),
ok.

-spec undefined_record_suggest(config()) -> ok.
undefined_record_suggest(Config) ->
Uri = ?config(undefined_record_suggest_uri, Config),
%% Don't care
Range = els_protocol:range(#{
from => {5, 4},
to => {5, 11}
}),
DestRange = els_protocol:range(#{
from => {4, 1},
to => {4, 1}
}),
Diag = #{
message => <<"record foo_bar undefined">>,
range => Range,
severity => 2,
source => <<"Compiler">>
},
#{result := Result} =
els_client:document_codeaction(Uri, Range, [Diag]),
Changes1 =
#{
binary_to_atom(Uri, utf8) =>
[
#{
range => Range,
newText => <<"#foobar">>
}
]
},
Changes2 =
#{
binary_to_atom(Uri, utf8) =>
[
#{
range => DestRange,
newText => <<"-record(foo_bar, {}).\n">>
}
]
},
Expected = [
#{
edit => #{changes => Changes1},
kind => <<"quickfix">>,
title => <<"Did you mean #foobar{}?">>
},
#{
edit => #{changes => Changes2},
kind => <<"quickfix">>,
title => <<"Define record foo_bar">>
}
],
?assertEqual(Expected, Result),
ok.

0 comments on commit d38dc66

Please sign in to comment.