• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

erlang-ls / erlang_ls / 2983

07 Oct 2024 03:08PM UTC coverage: 67.284% (-0.1%) from 67.425%
2983

push

github

plux
ok now?

4 of 5 new or added lines in 2 files covered. (80.0%)

63 existing lines in 7 files now uncovered.

4650 of 6911 relevant lines covered (67.28%)

13332.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.65
/apps/els_lsp/src/els_code_navigation.erl
1
%%==============================================================================
2
%% Code Navigation
3
%%==============================================================================
4
-module(els_code_navigation).
5

6
%%==============================================================================
7
%% Exports
8
%%==============================================================================
9

10
%% API
11
-export([
12
    goto_definition/2,
13
    find_in_scope/2
14
]).
15

16
%%==============================================================================
17
%% Includes
18
%%==============================================================================
19
-include("els_lsp.hrl").
20

21
%%==============================================================================
22
%% Type definitions
23
%%==============================================================================
24
-type goto_definition() :: [{uri(), els_poi:poi()}].
25
-export_type([goto_definition/0]).
26

27
%%==============================================================================
28
%% API
29
%%==============================================================================
30

31
-spec goto_definition(uri(), els_poi:poi()) ->
32
    {ok, goto_definition()} | {error, any()}.
33
goto_definition(
34
    Uri,
35
    Var = #{kind := variable}
36
) ->
37
    %% This will naively try to find the definition of a variable by finding the
38
    %% first occurrence of the variable in variable scope.
39
    case find_in_scope(Uri, Var) of
14✔
40
        [Var | _] -> {error, already_at_definition};
×
41
        [POI | _] -> {ok, [{Uri, POI}]};
14✔
42
        % Probably due to parse error
43
        [] -> {error, nothing_in_scope}
×
44
    end;
45
goto_definition(
46
    _Uri,
47
    #{kind := Kind, id := {M, F, A}}
48
) when
49
    Kind =:= application;
50
    Kind =:= implicit_fun;
51
    Kind =:= import_entry
52
->
53
    case els_utils:find_module(M) of
24✔
54
        {ok, Uri} -> defs_to_res(find(Uri, function, {F, A}));
23✔
55
        {error, Error} -> {error, Error}
1✔
56
    end;
57
goto_definition(
58
    Uri,
59
    #{kind := Kind, id := {F, A}} = POI
60
) when
61
    Kind =:= application;
62
    Kind =:= implicit_fun;
63
    Kind =:= export_entry;
64
    Kind =:= nifs_entry
65
->
66
    %% try to find local function first
67
    %% fall back to bif search if unsuccessful
68
    case find(Uri, function, {F, A}) of
48✔
69
        [] ->
70
            case is_imported_bif(Uri, F, A) of
5✔
71
                true ->
72
                    goto_definition(Uri, POI#{id := {erlang, F, A}});
2✔
73
                false ->
74
                    {error, not_found}
3✔
75
            end;
76
        Result ->
77
            defs_to_res(Result)
43✔
78
    end;
79
goto_definition(
80
    Uri,
81
    #{kind := atom, id := Id}
82
) ->
83
    %% Two interesting cases for atoms: functions and modules.
84
    %% We return all function defs with any arity combined with module defs.
85
    DefsFun = find(Uri, function, {Id, any_arity}),
7✔
86
    case els_utils:find_module(Id) of
7✔
87
        {ok, ModUri} -> defs_to_res(DefsFun ++ find(ModUri, module, Id));
6✔
88
        {error, _Error} -> defs_to_res(DefsFun)
1✔
89
    end;
90
goto_definition(
91
    _Uri,
92
    #{kind := Kind, id := Module}
93
) when
94
    Kind =:= behaviour;
95
    Kind =:= module
96
->
97
    case els_utils:find_module(Module) of
1✔
98
        {ok, Uri} -> defs_to_res(find(Uri, module, Module));
1✔
99
        {error, Error} -> {error, Error}
×
100
    end;
101
goto_definition(
102
    Uri,
103
    #{
104
        kind := macro,
105
        id := {MacroName, _Arity} = Define
106
    } = POI
107
) ->
108
    case find(Uri, define, Define) of
15✔
109
        [] ->
110
            goto_definition(Uri, POI#{id => MacroName});
3✔
111
        Else ->
112
            defs_to_res(Else)
12✔
113
    end;
114
goto_definition(Uri, #{kind := macro, id := Define}) ->
115
    defs_to_res(find(Uri, define, Define));
42✔
116
goto_definition(Uri, #{kind := record_expr, id := Record}) ->
117
    defs_to_res(find(Uri, record, Record));
67✔
118
goto_definition(Uri, #{kind := record_field, id := {Record, Field}}) ->
119
    defs_to_res(find(Uri, record_def_field, {Record, Field}));
48✔
120
goto_definition(_Uri, #{kind := Kind, id := Id}) when
121
    Kind =:= include;
122
    Kind =:= include_lib
123
->
124
    case els_utils:find_header(els_utils:filename_to_atom(Id)) of
2✔
125
        {ok, Uri} -> {ok, [{Uri, beginning()}]};
2✔
126
        {error, Error} -> {error, Error}
×
127
    end;
128
goto_definition(_Uri, #{kind := type_application, id := {M, T, A}}) ->
129
    case els_utils:find_module(M) of
13✔
130
        {ok, Uri} -> defs_to_res(find(Uri, type_definition, {T, A}));
13✔
131
        {error, Error} -> {error, Error}
×
132
    end;
133
goto_definition(Uri, #{kind := Kind, id := {T, A}}) when
134
    Kind =:= type_application; Kind =:= export_type_entry
135
->
136
    defs_to_res(find(Uri, type_definition, {T, A}));
27✔
137
goto_definition(_Uri, #{kind := parse_transform, id := Module}) ->
138
    case els_utils:find_module(Module) of
1✔
139
        {ok, Uri} -> defs_to_res(find(Uri, module, Module));
1✔
140
        {error, Error} -> {error, Error}
×
141
    end;
142
goto_definition(Uri, #{kind := callback, id := Id}) ->
143
    defs_to_res(find(Uri, callback, Id));
1✔
144
goto_definition(_Filename, _) ->
145
    {error, not_found}.
3✔
146

147
-spec is_imported_bif(uri(), atom(), non_neg_integer() | any_arity) -> boolean().
148
is_imported_bif(_Uri, _F, any_arity) ->
149
    false;
1✔
150
is_imported_bif(_Uri, F, A) ->
151
    OldBif = erl_internal:old_bif(F, A),
4✔
152
    Bif = erl_internal:bif(F, A),
4✔
153
    case {OldBif, Bif} of
4✔
154
        %% Cannot be shadowed, always imported
155
        {true, true} ->
156
            true;
2✔
157
        %% It's not a BIF at all
158
        {false, false} ->
159
            false;
2✔
160
        %% The hard case, just jump to the bif for now
161
        {_, _} ->
162
            true
×
163
    end.
164

165
-spec defs_to_res([{uri(), els_poi:poi()}]) -> {ok, [{uri(), els_poi:poi()}]} | {error, not_found}.
166
defs_to_res([]) -> {error, not_found};
16✔
167
defs_to_res(Defs) -> {ok, Defs}.
261✔
168

169
-spec find(uri() | [uri()], els_poi:poi_kind(), any()) ->
170
    [{uri(), els_poi:poi()}].
171
find(UriOrUris, Kind, Data) ->
172
    find(UriOrUris, Kind, Data, sets:new()).
301✔
173

174
-spec find(uri() | [uri()], els_poi:poi_kind(), any(), sets:set(binary())) ->
175
    [{uri(), els_poi:poi()}].
176
find([], _Kind, _Data, _AlreadyVisited) ->
177
    [];
26✔
178
find([Uri | Uris0], Kind, Data, AlreadyVisited) ->
179
    case sets:is_element(Uri, AlreadyVisited) of
528✔
180
        true ->
181
            find(Uris0, Kind, Data, AlreadyVisited);
21✔
182
        false ->
183
            AlreadyVisited2 = sets:add_element(Uri, AlreadyVisited),
507✔
184
            case els_utils:lookup_document(Uri) of
507✔
185
                {ok, Document} ->
186
                    find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited2);
499✔
187
                {error, _Error} ->
188
                    find(Uris0, Kind, Data, AlreadyVisited2)
×
189
            end
190
    end;
191
find(Uri, Kind, Data, AlreadyVisited) ->
192
    find([Uri], Kind, Data, AlreadyVisited).
301✔
193

194
-spec find_in_document(
195
    uri() | [uri()],
196
    els_dt_document:item(),
197
    els_poi:poi_kind(),
198
    any(),
199
    sets:set(binary())
200
) ->
201
    [{uri(), els_poi:poi()}].
202
find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited) ->
203
    POIs = els_dt_document:pois(Document, [Kind]),
499✔
204
    Defs = [POI || #{id := Id} = POI <- POIs, Id =:= Data],
499✔
205
    {AllDefs, MultipleDefs} =
499✔
206
        case Data of
207
            {_, any_arity} when
208
                Kind =:= function;
209
                Kind =:= define;
210
                Kind =:= type_definition
211
            ->
212
                %% Including defs with any arity
213
                AnyArity = [
30✔
214
                    POI
6✔
215
                 || #{id := {F, _}} = POI <- POIs, Data =:= {F, any_arity}
30✔
216
                ],
217
                {AnyArity, true};
30✔
218
            _ ->
219
                {Defs, false}
469✔
220
        end,
221
    case AllDefs of
499✔
222
        [] ->
223
            case maybe_imported(Document, Kind, Data) of
234✔
224
                [] ->
225
                    find(
232✔
226
                        lists:usort(include_uris(Document) ++ Uris0),
227
                        Kind,
228
                        Data,
229
                        AlreadyVisited
230
                    );
231
                Else ->
232
                    Else
2✔
233
            end;
234
        Definitions ->
235
            SortedDefs = els_poi:sort(Definitions),
265✔
236
            case MultipleDefs of
265✔
237
                true ->
238
                    %% This will be the case only when the user tries to
239
                    %% navigate to the definition of an atom or a
240
                    %% function/type/macro of wrong arity.
241
                    [{Uri, POI} || POI <- SortedDefs];
5✔
242
                false ->
243
                    %% In the general case, we return only one def
244
                    [{Uri, hd(SortedDefs)}]
260✔
245
            end
246
    end.
247

248
-spec include_uris(els_dt_document:item()) -> [uri()].
249
include_uris(Document) ->
250
    POIs = els_dt_document:pois(Document, [include, include_lib]),
232✔
251
    lists:foldl(fun add_include_uri/2, [], POIs).
232✔
252

253
-spec add_include_uri(els_poi:poi(), [uri()]) -> [uri()].
254
add_include_uri(#{id := Id}, Acc) ->
255
    case els_utils:find_header(els_utils:filename_to_atom(Id)) of
342✔
256
        {ok, Uri} -> [Uri | Acc];
342✔
UNCOV
257
        {error, _Error} -> Acc
×
258
    end.
259

260
-spec beginning() -> #{range => #{from => {1, 1}, to => {1, 1}}}.
261
beginning() ->
262
    #{range => #{from => {1, 1}, to => {1, 1}}}.
2✔
263

264
%% @doc check for a match in any of the module imported functions.
265
-spec maybe_imported(els_dt_document:item(), els_poi:poi_kind(), any()) ->
266
    [{uri(), els_poi:poi()}].
267
maybe_imported(Document, function, {F, A}) ->
268
    POIs = els_dt_document:pois(Document, [import_entry]),
26✔
269
    case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
26✔
270
        [] ->
271
            [];
24✔
272
        [{M, F, A} | _] ->
273
            case els_utils:find_module(M) of
2✔
274
                {ok, Uri0} -> find(Uri0, function, {F, A});
2✔
UNCOV
275
                {error, not_found} -> []
×
276
            end
277
    end;
278
maybe_imported(_Document, _Kind, _Data) ->
279
    [].
208✔
280

281
-spec find_in_scope(uri(), els_poi:poi()) -> [els_poi:poi()].
282
find_in_scope(
283
    Uri,
284
    #{kind := variable, id := VarId, range := VarRange}
285
) ->
286
    {ok, Document} = els_utils:lookup_document(Uri),
62✔
287
    LcPOIs = els_poi:sort(els_dt_document:pois(Document, [list_comp])),
62✔
288
    VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
62✔
289
    ScopeRange = els_scope:variable_scope_range(VarRange, Document),
62✔
290
    MatchInScope = [
62✔
291
        POI
186✔
292
     || #{id := Id} = POI <- pois_in(VarPOIs, ScopeRange),
62✔
293
        Id =:= VarId
224✔
294
    ],
295
    %% Handle special case if variable POI is inside list comprehension (LC)
296
    MatchingLcPOIs = pois_contains(LcPOIs, VarRange),
62✔
297
    case find_in_scope_list_comp(MatchingLcPOIs, MatchInScope) of
62✔
298
        [] ->
299
            MatchInScope -- find_in_scope_list_comp(LcPOIs, MatchInScope);
40✔
300
        MatchInLc ->
301
            MatchInLc
22✔
302
    end.
303

304
-spec find_in_scope_list_comp([els_poi:poi()], [els_poi:poi()]) ->
305
    [els_poi:poi()].
306
find_in_scope_list_comp([], _VarPOIs) ->
307
    %% No match in LC, use regular scope
308
    [];
75✔
309
find_in_scope_list_comp([LcPOI | LcPOIs], VarPOIs) ->
310
    #{data := #{pattern_ranges := PatRanges}, range := LcRange} = LcPOI,
78✔
311
    VarsInLc = pois_in(VarPOIs, LcRange),
78✔
312
    {PatVars, OtherVars} =
78✔
313
        lists:partition(
314
            fun(#{range := Range}) ->
315
                lists:any(
67✔
316
                    fun(PatRange) ->
317
                        els_range:in(Range, PatRange)
80✔
318
                    end,
319
                    PatRanges
320
                )
321
            end,
322
            VarsInLc
323
        ),
324
    case PatVars of
78✔
325
        [] ->
326
            %% Didn't find any patterned vars in this LC, try the next one
327
            find_in_scope_list_comp(LcPOIs, VarPOIs);
51✔
328
        _ ->
329
            %% Put pattern vars first to make goto definition work
330
            PatVars ++ OtherVars
27✔
331
    end.
332

333
-spec pois_in([els_poi:poi()], els_poi:poi_range()) ->
334
    [els_poi:poi()].
335
pois_in(POIs, Range) ->
336
    [POI || #{range := R} = POI <- POIs, els_range:in(R, Range)].
140✔
337

338
-spec pois_contains([els_poi:poi()], els_poi:poi_range()) ->
339
    [els_poi:poi()].
340
pois_contains(POIs, Range) ->
341
    [POI || #{range := R} = POI <- POIs, els_range:in(Range, R)].
62✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc