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

erlang-ls / erlang_ls / 3017

11 Oct 2024 04:55PM UTC coverage: 67.395% (+0.06%) from 67.34%
3017

push

github

web-flow
Improvements to extract function (#1563)

* Ignore variables inside funs() and list comprehensions
* Don't suggest to extract function unless it contains more than one poi

29 of 30 new or added lines in 4 files covered. (96.67%)

1 existing line in 1 file now uncovered.

4752 of 7051 relevant lines covered (67.39%)

13132.48 hits per line

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

80.51
/apps/els_lsp/src/els_code_actions.erl
1
-module(els_code_actions).
2
-export([
3
    extract_function/2,
4
    create_function/4,
5
    export_function/4,
6
    fix_module_name/4,
7
    ignore_variable/4,
8
    remove_macro/4,
9
    remove_unused/4,
10
    suggest_variable/4,
11
    fix_atom_typo/4,
12
    undefined_callback/4,
13
    define_macro/4,
14
    define_record/4,
15
    add_include_lib_macro/4,
16
    add_include_lib_record/4,
17
    suggest_macro/4,
18
    suggest_record/4,
19
    suggest_record_field/4,
20
    suggest_function/4,
21
    suggest_module/4,
22
    bump_variables/2
23
]).
24

25
-include("els_lsp.hrl").
26
-spec create_function(uri(), range(), binary(), [binary()]) -> [map()].
27
create_function(Uri, Range0, _Data, [UndefinedFun]) ->
28
    {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
3✔
29
    Range = els_range:to_poi_range(Range0),
3✔
30
    Indent = guess_indentation(string:lexemes(Text, "\n")),
3✔
31
    IndentStr = lists:duplicate(Indent, 32),
3✔
32
    FunPOIs = els_dt_document:pois(Document, [function]),
3✔
33
    %% Figure out which function the error was found in, as we want to
34
    %% create the function right after the current function.
35
    %% (Where the wrapping_range ends)
36
    case
3✔
37
        [
38
            R
3✔
39
         || #{data := #{wrapping_range := R}} <- FunPOIs,
3✔
40
            els_range:in(Range, R)
12✔
41
        ]
42
    of
43
        [#{to := {Line, _}} | _] ->
44
            [Name, ArityBin] = string:split(UndefinedFun, "/"),
3✔
45
            Arity = binary_to_integer(ArityBin),
3✔
46
            Args = format_args(Document, Arity, Range),
3✔
47
            SpecAndFun = io_lib:format(
3✔
48
                "~s(~s) ->\n~sok.\n\n",
49
                [Name, Args, IndentStr]
50
            ),
51
            [
52
                make_edit_action(
3✔
53
                    Uri,
54
                    <<"Create function ", UndefinedFun/binary>>,
55
                    ?CODE_ACTION_KIND_QUICKFIX,
56
                    iolist_to_binary(SpecAndFun),
57
                    els_protocol:range(#{
58
                        from => {Line + 1, 1},
59
                        to => {Line + 1, 1}
60
                    })
61
                )
62
            ];
63
        _ ->
64
            []
×
65
    end.
66

67
-spec export_function(uri(), range(), binary(), [binary()]) -> [map()].
68
export_function(Uri, _Range, _Data, [UnusedFun]) ->
69
    {ok, Document} = els_utils:lookup_document(Uri),
1✔
70
    case els_poi:sort(els_dt_document:pois(Document, [module, export])) of
1✔
71
        [] ->
72
            [];
×
73
        POIs ->
74
            #{range := #{to := {Line, _Col}}} = lists:last(POIs),
1✔
75
            Pos = {Line + 1, 1},
1✔
76
            [
77
                make_edit_action(
1✔
78
                    Uri,
79
                    <<"Export ", UnusedFun/binary>>,
80
                    ?CODE_ACTION_KIND_QUICKFIX,
81
                    <<"-export([", UnusedFun/binary, "]).\n">>,
82
                    els_protocol:range(#{from => Pos, to => Pos})
83
                )
84
            ]
85
    end.
86

87
-spec ignore_variable(uri(), range(), binary(), [binary()]) -> [map()].
88
ignore_variable(Uri, Range, _Data, [UnusedVariable]) ->
89
    {ok, Document} = els_utils:lookup_document(Uri),
1✔
90
    POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
1✔
91
    case ensure_range(els_range:to_poi_range(Range), UnusedVariable, POIs) of
1✔
92
        {ok, VarRange} ->
93
            [
94
                make_edit_action(
1✔
95
                    Uri,
96
                    <<"Add '_' to '", UnusedVariable/binary, "'">>,
97
                    ?CODE_ACTION_KIND_QUICKFIX,
98
                    <<"_", UnusedVariable/binary>>,
99
                    els_protocol:range(VarRange)
100
                )
101
            ];
102
        error ->
103
            []
×
104
    end.
105

106
-spec add_include_lib_macro(uri(), range(), binary(), [binary()]) -> [map()].
107
add_include_lib_macro(Uri, Range, _Data, [Macro0]) ->
108
    {Name, Id} =
4✔
109
        case string:split(Macro0, "/") of
110
            [MacroBin] ->
111
                Name0 = binary_to_atom(MacroBin, utf8),
2✔
112
                {Name0, Name0};
2✔
113
            [MacroBin, ArityBin] ->
114
                Name0 = binary_to_atom(MacroBin, utf8),
2✔
115
                Arity = binary_to_integer(ArityBin),
2✔
116
                {Name0, {Name0, Arity}}
2✔
117
        end,
118
    add_include_file(Uri, Range, 'define', Name, Id).
4✔
119

120
-spec define_macro(uri(), range(), binary(), [binary()]) -> [map()].
121
define_macro(Uri, Range, _Data, [Macro0]) ->
122
    {ok, Document} = els_utils:lookup_document(Uri),
4✔
123
    NewText =
4✔
124
        case string:split(Macro0, "/") of
125
            [MacroBin] ->
126
                <<"-define(", MacroBin/binary, ", undefined).\n">>;
2✔
127
            [MacroBin, ArityBin] ->
128
                Arity = binary_to_integer(ArityBin),
2✔
129
                Args = string:join(lists:duplicate(Arity, "_"), ", "),
2✔
130
                list_to_binary(
2✔
131
                    ["-define(", MacroBin, "(", Args, "), undefined).\n"]
132
                )
133
        end,
134
    #{from := Pos} = els_range:to_poi_range(Range),
4✔
135
    BeforeRange = #{from => {1, 1}, to => Pos},
4✔
136
    POIs = els_dt_document:pois_in_range(
4✔
137
        Document,
138
        [module, include, include_lib, define],
139
        BeforeRange
140
    ),
141
    case POIs of
4✔
142
        [] ->
143
            [];
×
144
        _ ->
145
            #{range := #{to := {Line, _}}} = lists:last(els_poi:sort(POIs)),
4✔
146
            [
147
                make_edit_action(
4✔
148
                    Uri,
149
                    <<"Define ", Macro0/binary>>,
150
                    ?CODE_ACTION_KIND_QUICKFIX,
151
                    NewText,
152
                    els_protocol:range(#{
153
                        to => {Line + 1, 1},
154
                        from => {Line + 1, 1}
155
                    })
156
                )
157
            ]
158
    end.
159

160
-spec define_record(uri(), range(), binary(), [binary()]) -> [map()].
161
define_record(Uri, Range, _Data, [Record]) ->
162
    {ok, Document} = els_utils:lookup_document(Uri),
2✔
163
    NewText = <<"-record(", Record/binary, ", {}).\n">>,
2✔
164
    #{from := Pos} = els_range:to_poi_range(Range),
2✔
165
    BeforeRange = #{from => {1, 1}, to => Pos},
2✔
166
    POIs = els_dt_document:pois_in_range(
2✔
167
        Document,
168
        [module, include, include_lib, record],
169
        BeforeRange
170
    ),
171
    case POIs of
2✔
172
        [] ->
173
            [];
×
174
        _ ->
175
            Line = end_line(lists:last(els_poi:sort(POIs))),
2✔
176
            [
177
                make_edit_action(
2✔
178
                    Uri,
179
                    <<"Define record ", Record/binary>>,
180
                    ?CODE_ACTION_KIND_QUICKFIX,
181
                    NewText,
182
                    els_protocol:range(#{
183
                        to => {Line + 1, 1},
184
                        from => {Line + 1, 1}
185
                    })
186
                )
187
            ]
188
    end.
189

190
-spec end_line(els_poi:poi()) -> non_neg_integer().
191
end_line(#{data := #{value_range := #{to := {Line, _}}}}) ->
192
    Line;
1✔
193
end_line(#{range := #{to := {Line, _}}}) ->
194
    Line.
1✔
195

196
-spec add_include_lib_record(uri(), range(), _, [binary()]) -> [map()].
197
add_include_lib_record(Uri, Range, _Data, [Record]) ->
198
    Name = binary_to_atom(Record, utf8),
2✔
199
    add_include_file(Uri, Range, 'record', Name, Name).
2✔
200

201
-spec add_include_file(uri(), range(), els_poi:poi_kind(), atom(), els_poi:poi_id()) -> [map()].
202
add_include_file(Uri, Range, Kind, Name, Id) ->
203
    %% TODO: Add support for -include() also
204
    CandidateUris =
6✔
205
        els_dt_document:find_candidates_with_otp(Name, 'header'),
206
    Uris = [
6✔
207
        CandidateUri
3✔
208
     || CandidateUri <- CandidateUris,
6✔
209
        contains_poi(Kind, CandidateUri, Id)
3✔
210
    ],
211
    Paths = els_include_paths:include_libs(Uris),
6✔
212
    {ok, Document} = els_utils:lookup_document(Uri),
6✔
213
    #{from := Pos} = els_range:to_poi_range(Range),
6✔
214
    BeforeRange = #{from => {1, 1}, to => Pos},
6✔
215
    case
6✔
216
        els_dt_document:pois_in_range(
217
            Document,
218
            [module, include, include_lib],
219
            BeforeRange
220
        )
221
    of
222
        [] ->
223
            [];
×
224
        POIs ->
225
            #{range := #{to := {Line, _}}} = lists:last(els_poi:sort(POIs)),
6✔
226
            [
6✔
227
                make_edit_action(
3✔
228
                    Uri,
229
                    <<"Add -include_lib(\"", Path/binary, "\")">>,
230
                    ?CODE_ACTION_KIND_QUICKFIX,
231
                    <<"-include_lib(\"", Path/binary, "\").\n">>,
232
                    els_protocol:range(#{to => {Line + 1, 1}, from => {Line + 1, 1}})
233
                )
234
             || Path <- Paths
6✔
235
            ]
236
    end.
237

238
-spec contains_poi(els_poi:poi_kind(), uri(), atom()) -> boolean().
239
contains_poi(Kind, Uri, Macro) ->
240
    {ok, Document} = els_utils:lookup_document(Uri),
3✔
241
    POIs = els_dt_document:pois(Document, [Kind]),
3✔
242
    lists:any(fun(#{id := Id}) -> Id =:= Macro end, POIs).
3✔
243

244
-spec suggest_variable(uri(), range(), binary(), [binary()]) -> [map()].
245
suggest_variable(Uri, Range, _Data, [Var]) ->
246
    %% Supply a quickfix to replace an unbound variable with the most similar
247
    %% variable name in scope.
248
    {ok, Document} = els_utils:lookup_document(Uri),
1✔
249
    POIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
1✔
250
    case ensure_range(els_range:to_poi_range(Range), Var, POIs) of
1✔
251
        {ok, VarRange} ->
252
            ScopeRange = els_scope:variable_scope_range(VarRange, Document),
1✔
253
            VarsInScope = [
1✔
254
                atom_to_binary(Id, utf8)
4✔
255
             || #{range := R, id := Id} <- POIs,
1✔
256
                els_range:in(R, ScopeRange),
8✔
257
                els_range:compare(R, VarRange)
4✔
258
            ],
259
            VariableDistances =
1✔
260
                [{els_utils:jaro_distance(V, Var), V} || V <- VarsInScope, V =/= Var],
3✔
261
            [
1✔
262
                make_edit_action(
1✔
263
                    Uri,
264
                    <<"Did you mean '", V/binary, "'?">>,
265
                    ?CODE_ACTION_KIND_QUICKFIX,
266
                    V,
267
                    els_protocol:range(VarRange)
268
                )
269
             || {Distance, V} <- lists:reverse(lists:usort(VariableDistances)),
1✔
270
                Distance > 0.8
2✔
271
            ];
272
        error ->
273
            []
×
274
    end.
275

276
-spec suggest_macro(uri(), range(), binary(), [binary()]) -> [map()].
277
suggest_macro(Uri, Range, _Data, [Macro]) ->
278
    %% Supply a quickfix to replace an unbound variable with the most similar
279
    %% variable name in scope.
280
    {ok, Document} = els_utils:lookup_document(Uri),
4✔
281
    POIs =
4✔
282
        els_scope:local_and_included_pois(Document, [define]) ++
283
            els_completion_provider:bif_pois(define),
284
    {Name, MacrosInScope} =
4✔
285
        case string:split(Macro, "/") of
286
            [Name0] ->
287
                {Name0, [atom_to_binary(Id) || #{id := Id} <- POIs, is_atom(Id)]};
2✔
288
            [Name0, ArityBin] ->
289
                Arity = binary_to_integer(ArityBin),
2✔
290
                {Name0, [
2✔
291
                    atom_to_binary(Id)
30✔
292
                 || #{id := {Id, A}} <- POIs,
2✔
293
                    is_atom(Id),
80✔
294
                    A =:= Arity
80✔
295
                ]}
296
        end,
297
    Distances =
4✔
298
        [{els_utils:jaro_distance(M, Name), M} || M <- MacrosInScope, M =/= Macro],
52✔
299
    [
4✔
300
        make_edit_action(
5✔
301
            Uri,
302
            <<"Did you mean '", M/binary, "'?">>,
303
            ?CODE_ACTION_KIND_QUICKFIX,
304
            <<"?", M/binary>>,
305
            Range
306
        )
307
     || {Distance, M} <- lists:reverse(lists:usort(Distances)),
4✔
308
        Distance > 0.8
40✔
309
    ].
310

311
-spec suggest_record(uri(), range(), binary(), [binary()]) -> [map()].
312
suggest_record(Uri, Range, _Data, [Record]) ->
313
    %% Supply a quickfix to replace an unrecognized record with the most similar
314
    %% record in scope.
315
    {ok, Document} = els_utils:lookup_document(Uri),
2✔
316
    POIs = els_scope:local_and_included_pois(Document, [record]),
2✔
317
    RecordsInScope = [atom_to_binary(Id) || #{id := Id} <- POIs, is_atom(Id)],
2✔
318
    Distances =
2✔
319
        [{els_utils:jaro_distance(Rec, Record), Rec} || Rec <- RecordsInScope, Rec =/= Record],
1✔
320
    [
2✔
321
        make_edit_action(
1✔
322
            Uri,
323
            <<"Did you mean #", Rec/binary, "{}?">>,
324
            ?CODE_ACTION_KIND_QUICKFIX,
325
            <<"#", Rec/binary>>,
326
            Range
327
        )
328
     || {Distance, Rec} <- lists:reverse(lists:usort(Distances)),
2✔
329
        Distance > 0.8
1✔
330
    ].
331

332
-spec suggest_record_field(uri(), range(), binary(), [binary()]) -> [map()].
333
suggest_record_field(Uri, Range, _Data, [Field, Record]) ->
334
    %% Supply a quickfix to replace an unrecognized record field with the most
335
    %% similar record field in Record.
336
    {ok, Document} = els_utils:lookup_document(Uri),
×
337
    POIs = els_scope:local_and_included_pois(Document, [record]),
×
338
    RecordId = binary_to_atom(Record, utf8),
×
339
    Fields = [
×
340
        atom_to_binary(F)
×
341
     || #{id := Id, data := #{field_list := Fs}} <- POIs,
×
342
        F <- Fs,
×
343
        Id =:= RecordId
×
344
    ],
345
    Distances =
×
346
        [{els_utils:jaro_distance(F, Field), F} || F <- Fields, F =/= Field],
×
347
    [
×
348
        make_edit_action(
×
349
            Uri,
350
            <<"Did you mean #", Record/binary, ".", F/binary, "?">>,
351
            ?CODE_ACTION_KIND_QUICKFIX,
352
            <<F/binary>>,
353
            Range
354
        )
355
     || {Distance, F} <- lists:reverse(lists:usort(Distances)),
×
356
        Distance > 0.8
×
357
    ].
358

359
-spec suggest_function(uri(), range(), binary(), [binary()]) -> [map()].
360
suggest_function(Uri, Range, _Data, [FunBin]) ->
361
    [ModNameBin, _ArityBin] = string:split(FunBin, <<"/">>),
3✔
362
    {{ok, Document}, NameBin} =
3✔
363
        case string:split(ModNameBin, <<":">>) of
364
            [ModBin, NameBin0] ->
365
                Mod = binary_to_atom(ModBin, utf8),
×
366
                {ok, ModUri} = els_utils:find_module(Mod),
×
367
                {els_utils:lookup_document(ModUri), NameBin0};
×
368
            [NameBin0] ->
369
                {els_utils:lookup_document(Uri), NameBin0}
3✔
370
        end,
371
    POIs = els_dt_document:pois(Document, [function]),
3✔
372
    Funs = [atom_to_binary(F) || #{id := {F, _A}} <- POIs],
3✔
373
    Distances =
3✔
374
        [{els_utils:jaro_distance(F, NameBin), F} || F <- Funs, F =/= NameBin],
12✔
375
    [
3✔
376
        make_edit_action(
×
377
            Uri,
378
            <<"Did you mean ", F/binary, "?">>,
379
            ?CODE_ACTION_KIND_QUICKFIX,
380
            F,
381
            Range
382
        )
383
     || {Distance, F} <- lists:reverse(lists:usort(Distances)),
3✔
384
        Distance > 0.8
12✔
385
    ].
386

387
-spec suggest_module(uri(), range(), binary(), [binary()]) -> [map()].
388
suggest_module(Uri, Range, _Data, [NameBin]) ->
389
    {ok, Items} = els_dt_document_index:find_by_kind(module),
×
390
    Mods = [atom_to_binary(M) || #{id := M} <- Items],
×
391
    Distances =
×
392
        [{els_utils:jaro_distance(M, NameBin), M} || M <- Mods, M =/= NameBin],
×
393
    [
×
394
        make_edit_action(
×
395
            Uri,
396
            <<"Did you mean ", M/binary, "?">>,
397
            ?CODE_ACTION_KIND_QUICKFIX,
398
            M,
399
            Range
400
        )
401
     || {Distance, M} <- lists:reverse(lists:usort(Distances)),
×
402
        Distance > 0.8
×
403
    ].
404

405
-spec fix_module_name(uri(), range(), binary(), [binary()]) -> [map()].
406
fix_module_name(Uri, Range0, _Data, [ModName, FileName]) ->
407
    {ok, Document} = els_utils:lookup_document(Uri),
1✔
408
    POIs = els_poi:sort(els_dt_document:pois(Document, [module])),
1✔
409
    case ensure_range(els_range:to_poi_range(Range0), ModName, POIs) of
1✔
410
        {ok, Range} ->
411
            [
412
                make_edit_action(
1✔
413
                    Uri,
414
                    <<"Change to -module(", FileName/binary, ").">>,
415
                    ?CODE_ACTION_KIND_QUICKFIX,
416
                    FileName,
417
                    els_protocol:range(Range)
418
                )
419
            ];
420
        error ->
421
            []
×
422
    end.
423

424
-spec remove_macro(uri(), range(), binary(), [binary()]) -> [map()].
425
remove_macro(Uri, Range, _Data, [Macro]) ->
426
    %% Supply a quickfix to remove the unused Macro
427
    {ok, Document} = els_utils:lookup_document(Uri),
1✔
428
    POIs = els_poi:sort(els_dt_document:pois(Document, [define])),
1✔
429
    case ensure_range(els_range:to_poi_range(Range), Macro, POIs) of
1✔
430
        {ok, MacroRange} ->
431
            LineRange = els_range:line(MacroRange),
1✔
432
            [
433
                make_edit_action(
1✔
434
                    Uri,
435
                    <<"Remove unused macro ", Macro/binary, ".">>,
436
                    ?CODE_ACTION_KIND_QUICKFIX,
437
                    <<"">>,
438
                    els_protocol:range(LineRange)
439
                )
440
            ];
441
        error ->
442
            []
×
443
    end.
444

445
-spec remove_unused(uri(), range(), binary(), [binary()]) -> [map()].
446
remove_unused(_Uri, _Range0, <<>>, [_Import]) ->
447
    [];
×
448
remove_unused(Uri, _Range0, Data, [Import]) ->
449
    {ok, Document} = els_utils:lookup_document(Uri),
1✔
450
    case els_range:inclusion_range(Data, Document) of
1✔
451
        {ok, UnusedRange} ->
452
            LineRange = els_range:line(UnusedRange),
1✔
453
            [
454
                make_edit_action(
1✔
455
                    Uri,
456
                    <<"Remove unused -include_lib(", Import/binary, ").">>,
457
                    ?CODE_ACTION_KIND_QUICKFIX,
458
                    <<>>,
459
                    els_protocol:range(LineRange)
460
                )
461
            ];
462
        error ->
463
            []
×
464
    end.
465

466
-spec fix_atom_typo(uri(), range(), binary(), [binary()]) -> [map()].
467
fix_atom_typo(Uri, Range, _Data, [Atom]) ->
468
    [
469
        make_edit_action(
×
470
            Uri,
471
            <<"Fix typo: ", Atom/binary>>,
472
            ?CODE_ACTION_KIND_QUICKFIX,
473
            Atom,
474
            Range
475
        )
476
    ].
477

478
-spec extract_function(uri(), range()) -> [map()].
479
extract_function(Uri, Range) ->
480
    {ok, [Document]} = els_dt_document:lookup(Uri),
25✔
481
    PoiRange = els_range:to_poi_range(Range),
25✔
482
    #{from := From = {Line, Column}, to := To} = PoiRange,
25✔
483
    %% We only want to extract if selection is large enough
484
    %% and cursor is inside a function
485
    POIsInRange = els_dt_document:pois_in_range(Document, PoiRange),
25✔
486
    #{text := Text} = Document,
25✔
487
    MarkedText = els_text:range(Text, From, To),
25✔
488
    case
25✔
489
        (length(POIsInRange) > 1 orelse
4✔
490
            els_text:is_keyword_expr(MarkedText)) andalso
21✔
491
            large_enough_range(From, To) andalso
5✔
492
            not contains_function_clause(Document, Line) andalso
5✔
493
            els_dt_document:wrapping_functions(Document, Line, Column) /= []
4✔
494
    of
495
        true ->
496
            [
497
                #{
4✔
498
                    title => <<"Extract function">>,
499
                    kind => <<"refactor.extract">>,
500
                    command => make_extract_function_command(Range, Uri)
501
                }
502
            ];
503
        false ->
504
            []
21✔
505
    end.
506

507
-spec bump_variables(uri(), range()) -> [map()].
508
bump_variables(Uri, Range) ->
509
    {ok, Document} = els_utils:lookup_document(Uri),
25✔
510
    #{from := {Line, Column}} = els_range:to_poi_range(Range),
25✔
511
    POIs = els_dt_document:get_element_at_pos(Document, Line, Column),
25✔
512
    case [POI || #{kind := variable} = POI <- POIs] of
25✔
513
        [] ->
514
            [];
23✔
515
        [#{id := Id, range := PoiRange} = _POI | _] ->
516
            Name = atom_to_binary(Id),
2✔
517
            case ends_with_digit(Name) of
2✔
518
                false ->
519
                    [];
2✔
520
                true ->
521
                    VarRange = els_protocol:range(PoiRange),
×
522
                    [
523
                        #{
×
524
                            title => <<"Bump variables: ", Name/binary>>,
525
                            kind => ?CODE_ACTION_KIND_QUICKFIX,
526
                            command => make_bump_variables_command(VarRange, Uri, Name)
527
                        }
528
                    ]
529
            end
530
    end.
531

532
-spec ends_with_digit(binary()) -> boolean().
533
ends_with_digit(Bin) ->
534
    N = binary:last(Bin),
2✔
535
    $0 =< N andalso N =< $9.
2✔
536

537
-spec make_extract_function_command(range(), uri()) -> map().
538
make_extract_function_command(Range, Uri) ->
539
    els_command:make_command(
4✔
540
        <<"Extract function">>,
541
        <<"refactor.extract">>,
542
        [#{uri => Uri, range => Range}]
543
    ).
544

545
-spec make_bump_variables_command(range(), uri(), binary()) -> map().
546
make_bump_variables_command(Range, Uri, Name) ->
547
    els_command:make_command(
×
548
        <<"Bump variables">>,
549
        <<"bump-variables">>,
550
        [#{uri => Uri, range => Range, name => Name}]
551
    ).
552

553
-spec contains_function_clause(
554
    els_dt_document:item(),
555
    non_neg_integer()
556
) -> boolean().
557
contains_function_clause(Document, Line) ->
558
    POIs = els_dt_document:get_element_at_pos(Document, Line, 1),
5✔
559
    lists:any(
5✔
560
        fun
561
            (#{kind := 'function_clause'}) ->
562
                true;
1✔
563
            (_) ->
564
                false
1✔
565
        end,
566
        POIs
567
    ).
568

569
-spec large_enough_range(pos(), pos()) -> boolean().
570
large_enough_range({Line, FromC}, {Line, ToC}) when (ToC - FromC) < 2 ->
UNCOV
571
    false;
×
572
large_enough_range(_From, _To) ->
573
    true.
5✔
574

575
-spec undefined_callback(uri(), range(), binary(), [binary()]) -> [map()].
576
undefined_callback(Uri, _Range, _Data, [_Function, Behaviour]) ->
577
    Title = <<"Add missing callbacks for: ", Behaviour/binary>>,
1✔
578
    [
579
        #{
1✔
580
            title => Title,
581
            kind => ?CODE_ACTION_KIND_QUICKFIX,
582
            command =>
583
                els_command:make_command(
584
                    Title,
585
                    <<"add-behaviour-callbacks">>,
586
                    [
587
                        #{
588
                            uri => Uri,
589
                            behaviour => Behaviour
590
                        }
591
                    ]
592
                )
593
        }
594
    ].
595

596
-spec ensure_range(els_poi:poi_range(), binary(), [els_poi:poi()]) ->
597
    {ok, els_poi:poi_range()} | error.
598
ensure_range(#{from := {Line, _}}, SubjectId, POIs) ->
599
    SubjectAtom = binary_to_atom(SubjectId, utf8),
4✔
600
    Ranges = [
4✔
601
        R
4✔
602
     || #{range := R, id := Id} <- POIs,
4✔
603
        els_range:in(R, #{from => {Line, 1}, to => {Line + 1, 1}}),
18✔
604
        Id =:= SubjectAtom
5✔
605
    ],
606
    case Ranges of
4✔
607
        [] ->
608
            error;
×
609
        [Range | _] ->
610
            {ok, Range}
4✔
611
    end.
612

613
-spec make_edit_action(uri(), binary(), binary(), binary(), range()) ->
614
    map().
615
make_edit_action(Uri, Title, Kind, Text, Range) ->
616
    #{
24✔
617
        title => Title,
618
        kind => Kind,
619
        edit => edit(Uri, Text, Range)
620
    }.
621

622
-spec edit(uri(), binary(), range()) -> workspace_edit().
623
edit(Uri, Text, Range) ->
624
    #{changes => #{Uri => [#{newText => Text, range => Range}]}}.
24✔
625

626
-spec format_args(
627
    els_dt_document:item(),
628
    non_neg_integer(),
629
    els_poi:poi_range()
630
) -> string().
631
format_args(Document, Arity, Range) ->
632
    %% Find the matching function application and extract
633
    %% argument names from it.
634
    AppPOIs = els_dt_document:pois(Document, [application]),
3✔
635
    Matches = [
3✔
636
        POI
3✔
637
     || #{range := R} = POI <- AppPOIs,
3✔
638
        els_range:in(R, Range)
12✔
639
    ],
640
    case Matches of
3✔
641
        [#{data := #{args := Args0}} | _] ->
642
            string:join([els_arg:name(A) || A <- Args0], ", ");
3✔
643
        [] ->
644
            string:join(lists:duplicate(Arity, "_"), ", ")
×
645
    end.
646

647
-spec guess_indentation([binary()]) -> pos_integer().
648
guess_indentation([]) ->
649
    2;
×
650
guess_indentation([A, B | Rest]) ->
651
    ACount = count_leading_spaces(A, 0),
15✔
652
    BCount = count_leading_spaces(B, 0),
15✔
653
    case {ACount, BCount} of
15✔
654
        {0, N} when N > 0 ->
655
            N;
3✔
656
        {_, _} ->
657
            guess_indentation([B | Rest])
12✔
658
    end.
659

660
-spec count_leading_spaces(binary(), non_neg_integer()) -> non_neg_integer().
661
count_leading_spaces(<<>>, _Acc) ->
662
    0;
×
663
count_leading_spaces(<<" ", Rest/binary>>, Acc) ->
664
    count_leading_spaces(Rest, 1 + Acc);
6✔
665
count_leading_spaces(<<_:8, _/binary>>, Acc) ->
666
    Acc.
30✔
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

© 2025 Coveralls, Inc