22
22
% % Includes
23
23
% %==============================================================================
24
24
-include (" els_lsp.hrl" ).
25
+ -include_lib (" kernel/include/logger.hrl" ).
25
26
% %==============================================================================
26
27
% % Callback Functions
27
28
% %==============================================================================
@@ -32,19 +33,41 @@ is_default() ->
32
33
33
34
-spec run (uri ()) -> [els_diagnostics :diagnostic ()].
34
35
run (Uri ) ->
35
- case els_utils :lookup_document (Uri ) of
36
- {error , _Error } ->
37
- [];
38
- {ok , Document } ->
39
- POIs = els_dt_document :pois (Document , [
40
- application ,
41
- implicit_fun ,
42
- import_entry ,
43
- export_entry ,
44
- nifs_entry
45
- ]),
46
- [make_diagnostic (POI ) || POI <- POIs , not has_definition (POI , Document )]
47
- end .
36
+ EnabledDiagnostics = els_diagnostics :enabled_diagnostics (),
37
+ CompilerEnabled = lists :member (<<" compiler" >>, EnabledDiagnostics ),
38
+ Start = erlang :monotonic_time (millisecond ),
39
+ Res =
40
+ case els_utils :lookup_document (Uri ) of
41
+ {error , _Error } ->
42
+ [];
43
+ {ok , Document } ->
44
+ POIs = els_dt_document :pois (Document , kinds ()),
45
+ Opts = #{
46
+ compiler_enabled => CompilerEnabled
47
+ },
48
+ {Diags , _Cache } =
49
+ lists :mapfoldl (
50
+ fun (#{id := Id } = POI , Cache ) ->
51
+ case find_in_cache (Id , Cache ) of
52
+ {ok , HasDef } ->
53
+ {[make_diagnostic (HasDef , POI )], Cache };
54
+ error ->
55
+ HasDef = has_definition (POI , Document , Opts ),
56
+ {
57
+ make_diagnostic (HasDef , POI ),
58
+ update_cache (HasDef , Id , Cache )
59
+ }
60
+ end
61
+ end ,
62
+ #{},
63
+ POIs
64
+ ),
65
+ lists :flatten (Diags )
66
+ end ,
67
+ End = erlang :monotonic_time (millisecond ),
68
+ Duration = End - Start ,
69
+ ? LOG_DEBUG (" Crossref done for ~p [duration: ~p ms]" , [els_uri :module (Uri ), Duration ]),
70
+ Res .
48
71
49
72
-spec source () -> binary ().
50
73
source () ->
@@ -53,106 +76,157 @@ source() ->
53
76
% %==============================================================================
54
77
% % Internal Functions
55
78
% %==============================================================================
56
- -spec make_diagnostic (els_poi :poi ()) -> els_diagnostics :diagnostic ().
57
- make_diagnostic (#{range := Range , id := Id }) ->
58
- Function =
59
- case Id of
60
- {F , A } -> lists :flatten (io_lib :format (" ~p /~p " , [F , A ]));
61
- {M , F , A } -> lists :flatten (io_lib :format (" ~p :~p /~p " , [M , F , A ]))
62
- end ,
63
- Message = els_utils :to_binary (
64
- io_lib :format (
65
- " Cannot find definition for function ~s " ,
66
- [Function ]
67
- )
68
- ),
79
+ -spec update_cache (true | {missing , function | module }, els_poi :poi_id (), map ()) -> map ().
80
+ update_cache ({missing , module }, {M , _F , _A }, Cache ) ->
81
+ % % Cache missing module to avoid repeated lookups
82
+ Cache #{M => missing };
83
+ update_cache (HasDef , Id , Cache ) ->
84
+ Cache #{Id => HasDef }.
85
+
86
+ -spec find_in_cache (els_poi :poi_id (), map ()) -> _ .
87
+ find_in_cache ({M , _F , _A }, Cache ) when is_map_key (M , Cache ) ->
88
+ {ok , {missing , module }};
89
+ find_in_cache (Id , Cache ) ->
90
+ maps :find (Id , Cache ).
91
+
92
+ -spec kinds () -> [els_poi :poi_kind ()].
93
+ kinds () ->
94
+ [
95
+ application ,
96
+ implicit_fun ,
97
+ import_entry ,
98
+ export_entry ,
99
+ nifs_entry
100
+ ].
101
+
102
+ -spec make_diagnostic (_ , els_poi :poi ()) -> [els_diagnostics :diagnostic ()].
103
+ make_diagnostic ({missing , Kind }, #{id := Id } = POI ) ->
104
+ Message = error_msg (Kind , Id ),
69
105
Severity = ? DIAGNOSTIC_ERROR ,
70
- els_diagnostics :make_diagnostic (
71
- els_protocol :range (Range ),
72
- Message ,
73
- Severity ,
74
- source ()
75
- ).
76
-
77
- -spec has_definition (els_poi :poi (), els_dt_document :item ()) -> boolean ().
78
- has_definition (
79
- #{
80
- kind := application ,
81
- id := {module_info , 0 }
82
- },
83
- _
84
- ) ->
106
+ [
107
+ els_diagnostics :make_diagnostic (
108
+ els_protocol :range (range (Kind , POI )),
109
+ Message ,
110
+ Severity ,
111
+ source ()
112
+ )
113
+ ];
114
+ make_diagnostic (true , _ ) ->
115
+ [].
116
+
117
+ -spec range (module | function , els_poi :poi ()) -> els_poi :poi_range ().
118
+ range (module , #{data := #{mod_range := Range }}) ->
119
+ Range ;
120
+ range (function , #{data := #{name_range := Range }}) ->
121
+ Range ;
122
+ range (_ , #{range := Range }) ->
123
+ Range .
124
+
125
+ -spec error_msg (module | function , els_poi :poi_id ()) -> binary ().
126
+ error_msg (module , {M , _F , _A }) ->
127
+ els_utils :to_binary (io_lib :format (" Cannot find module ~p " , [M ]));
128
+ error_msg (function , Id ) ->
129
+ els_utils :to_binary (io_lib :format (" Cannot find definition for function ~s " , [id_str (Id )])).
130
+
131
+ -spec id_str (els_poi :poi_id ()) -> binary ().
132
+ id_str (Id ) ->
133
+ case Id of
134
+ {F , A } -> lists :flatten (io_lib :format (" ~p /~p " , [F , A ]));
135
+ {M , F , A } -> lists :flatten (io_lib :format (" ~p :~p /~p " , [M , F , A ]))
136
+ end .
137
+
138
+ -spec has_definition (els_poi :poi (), els_dt_document :item (), _ ) ->
139
+ true | {missing , function | module }.
140
+ has_definition (#{data := #{imported := true }}, _Document , _Opts ) ->
141
+ % % Call to a bif
85
142
true ;
86
- has_definition (
87
- #{
88
- kind := application ,
89
- id := {module_info , 1 }
90
- },
91
- _
92
- ) ->
143
+ has_definition (#{id := {module_info , 0 }}, _ , _ ) ->
93
144
true ;
94
- has_definition (
95
- #{
96
- kind := application ,
97
- data := #{mod_is_variable := true }
98
- },
99
- _
100
- ) ->
145
+ has_definition (#{id := {module_info , 1 }}, _ , _ ) ->
101
146
true ;
102
- has_definition (
103
- #{
104
- kind := application ,
105
- id := {Module , module_info , Arity }
106
- },
107
- _
108
- ) when Arity =:= 0 ; Arity =:= 1 ->
109
- {ok , []} =/= els_dt_document_index :lookup (Module );
110
- has_definition (
111
- #{
112
- kind := application ,
113
- id := {record_info , 2 }
114
- },
115
- _
116
- ) ->
147
+ has_definition (#{data := #{mod_is_variable := true }}, _ , _ ) ->
117
148
true ;
118
- has_definition (
119
- #{
120
- kind := application ,
121
- id := {behaviour_info , 1 }
122
- },
123
- _
124
- ) ->
149
+ has_definition (#{data := #{fun_is_variable := true }}, _ , _ ) ->
125
150
true ;
126
- has_definition (
127
- #{
128
- kind := application ,
129
- data := #{fun_is_variable := true }
130
- },
131
- _
132
- ) ->
151
+ has_definition (#{id := {Module , module_info , Arity }}, _ , _ ) when Arity =:= 0 ; Arity =:= 1 ->
152
+ case els_dt_document_index :lookup (Module ) of
153
+ {ok , []} ->
154
+ {missing , module };
155
+ {ok , _ } ->
156
+ true
157
+ end ;
158
+ has_definition (#{id := {record_info , 2 }}, _ , _ ) ->
159
+ true ;
160
+ has_definition (#{id := {behaviour_info , 1 }}, _ , _ ) ->
161
+ true ;
162
+ has_definition (#{id := {lager , Level , Arity }}, _ , _ ) ->
163
+ lager_definition (Level , Arity );
164
+ has_definition (#{id := {lists , append , 1 }}, _ , _ ) ->
165
+ % % lists:append/1 isn't indexed for some reason
133
166
true ;
134
167
has_definition (
168
+ #{id := {F , A }} = POI ,
169
+ Document ,
135
170
#{
136
- kind := application ,
137
- id := {lager , Level , Arity }
138
- },
139
- _
171
+ % % Compiler already checks local function calls
172
+ compiler_enabled := false
173
+ }
140
174
) ->
141
- lager_definition ( Level , Arity );
142
- has_definition ( POI , #{ uri : = Uri }) ->
143
- case els_code_navigation : goto_definition ( Uri , POI ) of
144
- { ok , _Defs } ->
175
+ Uri = els_dt_document : uri ( Document ),
176
+ MFA = { els_uri : module ( Uri ), F , A },
177
+ case function_lookup ( MFA ) of
178
+ true ->
145
179
true ;
146
- {error , _Error } ->
180
+ false ->
181
+ case els_code_navigation :goto_definition (Uri , POI ) of
182
+ {ok , _Defs } ->
183
+ true ;
184
+ {error , _Error } ->
185
+ {missing , function }
186
+ end
187
+ end ;
188
+ has_definition (#{id := {M , _F , _A } = MFA } = POI , _Document , _Opts ) ->
189
+ case function_lookup (MFA ) of
190
+ true ->
191
+ true ;
192
+ false ->
193
+ case els_utils :find_module (M ) of
194
+ {ok , Uri } ->
195
+ case els_code_navigation :goto_definition (Uri , POI ) of
196
+ {ok , _Defs } ->
197
+ true ;
198
+ {error , _Error } ->
199
+ {missing , function }
200
+ end ;
201
+ {error , _ } ->
202
+ {missing , module }
203
+ end
204
+ end ;
205
+ has_definition (_POI , #{uri := _Uri }, _Opts ) ->
206
+ true .
207
+
208
+ -spec function_lookup (mfa ()) -> boolean ().
209
+ function_lookup (MFA ) ->
210
+ case els_db :lookup (els_dt_functions :name (), MFA ) of
211
+ {ok , []} ->
212
+ false ;
213
+ {ok , _ } ->
214
+ true ;
215
+ _ ->
147
216
false
148
217
end .
149
218
150
219
-spec lager_definition (atom (), integer ()) -> boolean ().
151
220
lager_definition (Level , Arity ) when Arity =:= 1 orelse Arity =:= 2 ->
152
- lists :member (Level , lager_levels ());
221
+ case lists :member (Level , lager_levels ()) of
222
+ true ->
223
+ true ;
224
+ false ->
225
+ {missing , function }
226
+ end ;
153
227
lager_definition (_ , _ ) ->
154
- false .
228
+ { missing , function } .
155
229
156
230
-spec lager_levels () -> [atom ()].
157
231
lager_levels () ->
158
- [debug , info , notice , warning , error , critical , alert , emergency ].
232
+ [debug , debug_unsafe , info , notice , warning , error , critical , alert , emergency ].
0 commit comments