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

[#22] Code Navigation and tests #33

Merged
merged 22 commits into from
Aug 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ab00aec
[#22] Add skeleton for code navigation test suite
robertoaloi Jul 31, 2019
8eec625
[#22] Run CT as part of CI
robertoaloi Jul 31, 2019
191198d
[#22] Add basic tests for code navigation
robertoaloi Jul 31, 2019
d63a396
[#22] Use apply to avoid dialyzer issue
robertoaloi Aug 1, 2019
cc66594
[#22] Export missing type
robertoaloi Jul 30, 2019
1e0351d
[#22] Address Dialyzer warnings
robertoaloi Aug 1, 2019
36b66b1
[#22] Re-enable Dialyzer in CI
robertoaloi Aug 1, 2019
1fbd8e9
[#22] Add unit test for behaviour navigation
robertoaloi Aug 1, 2019
471b8ef
[#22] Remove un-necessary parameter
robertoaloi Aug 1, 2019
733aa91
[#22] Introduce erlang_ls_tree module
robertoaloi Aug 1, 2019
b1fead4
[#22] Add list of tests TODO
robertoaloi Aug 1, 2019
0a9d9ba
[#22] Add behaviour data file for tests
robertoaloi Aug 1, 2019
5f3bdd4
[#22] Move functions to poi module, refactor
robertoaloi Aug 5, 2019
90cae6b
[#22] Introduce code navigation module
robertoaloi Aug 5, 2019
73a7682
[#22] Move annotate_file function to tree module
robertoaloi Aug 5, 2019
d4a0539
[#22] Move list/match functions to poi module
robertoaloi Aug 5, 2019
a346a6b
[#22] Do not expose Uri to code navigation module
robertoaloi Aug 6, 2019
32883be
[#22] Do not return Uri from tree module
robertoaloi Aug 6, 2019
54cca9b
[#22] Refactor navigation API
robertoaloi Aug 6, 2019
856fc79
[#22] Basic test for include navigation
robertoaloi Aug 6, 2019
ae6304d
[#22] Add tests for code navigation
robertoaloi Aug 6, 2019
8abf59f
[#22] Fix Dialyzer issue
robertoaloi Aug 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ otp_release:
- 22.0
- 21.3
script:
- rebar3 as test do compile, proper --cover, xref, cover, coveralls send
- rebar3 as test do compile, ct, proper --cover, dialyzer, xref, cover, coveralls send
cache:
directories:
- "$HOME/.cache/rebar3"
3 changes: 3 additions & 0 deletions priv/code_navigation/include/code_navigation.hrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-record(included_record_a, {field_a, field_b}).

-define(INCLUDED_MACRO_A, included_macro_a).
3 changes: 3 additions & 0 deletions priv/code_navigation/src/behaviour_a.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-module(behaviour_a).

-callback callback_a() -> ok.
29 changes: 29 additions & 0 deletions priv/code_navigation/src/code_navigation.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-module(code_navigation).

-behaviour(behaviour_a).

-export([ function_a/0 ]).

%% behaviour_a callbacks
-export([ callback_a/0 ]).

-include("code_navigation.hrl").
-include_lib("code_navigation/include/code_navigation.hrl").

-record(record_a, {field_a, field_b}).

-define(MACRO_A, macro_a).

function_a() ->
function_b(),
#record_a{}.

function_b() ->
?MACRO_A.

callback_a() ->
ok.

function_c() ->
code_navigation_extra:do(test),
length([1, 2, 3]).
6 changes: 6 additions & 0 deletions priv/code_navigation/src/code_navigation_extra.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(code_navigation_extra).

-export([ do/1 ]).

do(_Config) ->
ok.
6 changes: 3 additions & 3 deletions src/erlang_ls_buffer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@ do_get_mfa(Text, Line, _Character) ->
{M, F, A}.

-spec do_get_element_at_pos(binary(), non_neg_integer(), non_neg_integer()) ->
[erlang_ls_parser:poi()].
[erlang_ls_poi:poi()].
do_get_element_at_pos(Text, Line, Column) ->
%% TODO: Cache tree
{ok, Tree} = erlang_ls_parser:parse(Text),
AnnotatedTree = erlang_ls_parser:annotate(Tree),
erlang_ls_parser:find_poi_by_pos(AnnotatedTree, {Line, Column}).
AnnotatedTree = erlang_ls_tree:annotate(Tree),
erlang_ls_poi:match_pos(AnnotatedTree, {Line, Column}).

-spec get_line_text(binary(), integer()) -> binary().
get_line_text(Text, Line) ->
Expand Down
173 changes: 173 additions & 0 deletions src/erlang_ls_code_navigation.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
%%==============================================================================
%% Code Navigation
%%==============================================================================
-module(erlang_ls_code_navigation).

%%==============================================================================
%% Exports
%%==============================================================================

%% API
-export([ goto_definition/2
, goto_definition/3
]).

%%==============================================================================
%% Includes
%%==============================================================================
-include("erlang_ls.hrl").

%%==============================================================================
%% Macros
%%==============================================================================
-define(OTP_INCLUDE_PATH, "/usr/local/Cellar/erlang/21.2.4/lib/erlang/lib").
%% TODO: Implement support for workspaces
-define(ERLANG_LS_PATH, "/Users/robert.aloi/git/github/erlang-ls/erlang_ls").
-define(TEST_APP_PATH, "/Users/robert.aloi/git/github/erlang-ls/test").
-define(DEPS_PATH, "/Users/robert.aloi/git/github/erlang-ls/erlang_ls/_build/debug/lib").

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

-spec goto_definition(binary(), erlang_ls_poi:poi()) ->
{ok, binary(), erlang_ls_poi:range()} | {error, any()}.
goto_definition(Filename, POI) ->
goto_definition(Filename, POI, full_path()).

-spec goto_definition(binary(), erlang_ls_poi:poi(), [string()]) ->
{ok, binary(), erlang_ls_poi:range()} | {error, any()}.
goto_definition( _Filename
, #{ info := {application, {M, _F, _A}} = Info }
, Path) ->
case erlang_ls_tree:annotate_file(filename(M), Path) of
{ok, FullName, AnnotatedTree} ->
case erlang_ls_poi:match(AnnotatedTree, definition(Info)) of
[#{ range := Range }] ->
{ok, FullName, Range};
[] ->
{error, not_found}
end;
{error, Error} ->
{error, Error}
end;
goto_definition( Filename
, #{ info := {application, {_F, _A}} = Info }
, Path) ->
case erlang_ls_tree:annotate_file(filename:basename(Filename), Path) of
{ok, FullName, AnnotatedTree} ->
case erlang_ls_poi:match(AnnotatedTree, definition(Info)) of
[#{ range := Range }] ->
{ok, FullName, Range};
[] ->
{error, not_found}
end;
{error, Error} ->
{error, Error}
end;
goto_definition(_Filename, #{ info := {behaviour, Behaviour} = Info }, Path) ->
search(filename(Behaviour), Path, definition(Info));
%% TODO: Eventually search everywhere and suggest a code lens to include a file
goto_definition(Filename, #{ info := {macro, _Define} = Info }, Path) ->
search(filename:basename(Filename), Path, definition(Info));
goto_definition(Filename, #{ info := {record_expr, _Record} = Info }, Path) ->
search(filename:basename(Filename), Path, definition(Info));
goto_definition(_Filename, #{ info := {include, Include0} }, Path) ->
Include = list_to_binary(string:trim(Include0, both, [$"])),
case erlang_ls_tree:annotate_file(Include, Path) of
{ok, FullName, _AnnotatedTree} ->
{ok, FullName, #{ from => {0, 0}, to => {0, 0} }};
{error, Error} ->
{error, Error}
end;
goto_definition(_Filename, #{ info := {include_lib, Include0} }, Path) ->
Include = list_to_binary(lists:last(filename:split(string:trim(Include0, both, [$"])))),
case erlang_ls_tree:annotate_file(Include, Path) of
{ok, FullName, _AnnotatedTree} ->
{ok, FullName, #{ from => {0, 0}, to => {0, 0} }};
{error, Error} ->
{error, Error}
end;
goto_definition(_Filename, _, _Path) ->
{error, not_found}.

-spec definition({atom(), any()}) -> {atom(), any()}.
definition({application, {_M, F, A}}) ->
{function, {F, A}};
definition({application, {F, A}}) ->
{function, {F, A}};
definition({behaviour, Behaviour}) ->
{module, Behaviour};
definition({macro, Define}) ->
{define, Define};
definition({record_expr, Record}) ->
{record, Record}.

-spec otp_path() -> [string()].
otp_path() ->
filelib:wildcard(filename:join([?OTP_INCLUDE_PATH, "*/src"])).

-spec app_path() -> [string()].
app_path() ->
[ filename:join([?TEST_APP_PATH, "src"])
, filename:join([?TEST_APP_PATH, "include"])
, filename:join([?ERLANG_LS_PATH, "src"])
, filename:join([?TEST_APP_PATH, "include"])
].

-spec deps_path() -> [string()].
deps_path() ->
filelib:wildcard(filename:join([?DEPS_PATH, "*/src"])).

full_path() ->
lists:append( [ app_path() , deps_path() , otp_path() ]).

%% Look for a definition recursively in a file and its includes.
-spec search(binary(), [string()], any()) ->
{ok, binary(), erlang_ls_poi:range()} | {error, any()}.
search(Filename, Path, Thing) ->
case erlang_ls_tree:annotate_file(Filename, Path) of
{ok, FullName, AnnotatedTree} ->
case find(AnnotatedTree, Thing) of
{error, not_found} ->
Includes = erlang_ls_poi:match_key(AnnotatedTree, include),
IncludeLibs = erlang_ls_poi:match_key(AnnotatedTree, include_lib),
search_in_includes(Includes ++ IncludeLibs, Path, Thing);
{ok, Range} ->
{ok, FullName, Range}
end;
{error, Error} ->
{error, Error}
end.

%% Look for a definition in a given tree
-spec find(erlang_ls_tree:tree(), any()) ->
{ok, erlang_ls_poi:range()} | {error, any()}.
find(AnnotatedTree, Thing) ->
case erlang_ls_poi:match(AnnotatedTree, Thing) of
[#{ range := Range }|_] ->
{ok, Range};
[] ->
{error, not_found}
end.

-spec search_in_includes([erlang_ls_poi:poi()], [string()], any()) ->
{ok, binary(), erlang_ls_poi:range()} | {error, any()}.
search_in_includes([], _Path, _Thing) ->
{error, not_found};
search_in_includes([#{info := Info}|T], Path, Thing) ->
Include = normalize_include(Info),
case search(list_to_binary(Include), Path, Thing) of
{error, _Error} -> search_in_includes(T, Path, Thing);
{ok, FullName, Range} -> {ok, FullName, Range}
end.

-spec normalize_include({atom(), string()}) -> string().
normalize_include({include, Include}) ->
string:trim(Include, both, [$"]);
normalize_include({include_lib, Include}) ->
lists:last(filename:split(string:trim(Include, both, [$"]))).

-spec filename(atom()) -> binary().
filename(Module) ->
list_to_binary(atom_to_list(Module) ++ ".erl").
1 change: 0 additions & 1 deletion src/erlang_ls_completion.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
-export([ record_definitions/0
]).

%% TODO: Store these in an ETS table
-spec record_definitions() -> [atom()].
record_definitions() ->
lists:usort(lists:flatten([record_definitions(B) || B <- all_beams()])).
Expand Down