diff --git a/apps/els_lsp/priv/code_navigation/src/diagnostics_xref_pseudo.erl b/apps/els_lsp/priv/code_navigation/src/diagnostics_xref_pseudo.erl index 841fce05..a52adcea 100644 --- a/apps/els_lsp/priv/code_navigation/src/diagnostics_xref_pseudo.erl +++ b/apps/els_lsp/priv/code_navigation/src/diagnostics_xref_pseudo.erl @@ -14,6 +14,7 @@ main() -> unknown_module:module_info(module), ?MODULE:behaviour_info(callbacks), lager:debug("log message", []), + lager:debug_unsafe("log message", []), lager:info("log message", []), lager:notice("log message", []), lager:warning("log message", []), @@ -23,6 +24,7 @@ main() -> lager:emergency("log message", []), lager:debug("log message"), + lager:debug_unsafe("log message", []), lager:info("log message"), lager:notice("log message"), lager:warning("log message"), diff --git a/apps/els_lsp/src/els_crossref_diagnostics.erl b/apps/els_lsp/src/els_crossref_diagnostics.erl index 9b08a15a..5e6b03f5 100644 --- a/apps/els_lsp/src/els_crossref_diagnostics.erl +++ b/apps/els_lsp/src/els_crossref_diagnostics.erl @@ -22,6 +22,7 @@ %% Includes %%============================================================================== -include("els_lsp.hrl"). +-include_lib("kernel/include/logger.hrl"). %%============================================================================== %% Callback Functions %%============================================================================== @@ -32,19 +33,41 @@ is_default() -> -spec run(uri()) -> [els_diagnostics:diagnostic()]. run(Uri) -> - case els_utils:lookup_document(Uri) of - {error, _Error} -> - []; - {ok, Document} -> - POIs = els_dt_document:pois(Document, [ - application, - implicit_fun, - import_entry, - export_entry, - nifs_entry - ]), - [make_diagnostic(POI) || POI <- POIs, not has_definition(POI, Document)] - end. + EnabledDiagnostics = els_diagnostics:enabled_diagnostics(), + CompilerEnabled = lists:member(<<"compiler">>, EnabledDiagnostics), + Start = erlang:monotonic_time(millisecond), + Res = + case els_utils:lookup_document(Uri) of + {error, _Error} -> + []; + {ok, Document} -> + POIs = els_dt_document:pois(Document, kinds()), + Opts = #{ + compiler_enabled => CompilerEnabled + }, + {Diags, _Cache} = + lists:mapfoldl( + fun(#{id := Id} = POI, Cache) -> + case find_in_cache(Id, Cache) of + {ok, HasDef} -> + {[make_diagnostic(HasDef, POI)], Cache}; + error -> + HasDef = has_definition(POI, Document, Opts), + { + make_diagnostic(HasDef, POI), + update_cache(HasDef, Id, Cache) + } + end + end, + #{}, + POIs + ), + lists:flatten(Diags) + end, + End = erlang:monotonic_time(millisecond), + Duration = End - Start, + ?LOG_DEBUG("Crossref done for ~p [duration: ~p ms]", [els_uri:module(Uri), Duration]), + Res. -spec source() -> binary(). source() -> @@ -53,106 +76,155 @@ source() -> %%============================================================================== %% Internal Functions %%============================================================================== --spec make_diagnostic(els_poi:poi()) -> els_diagnostics:diagnostic(). -make_diagnostic(#{range := Range, id := Id}) -> - Function = - case Id of - {F, A} -> lists:flatten(io_lib:format("~p/~p", [F, A])); - {M, F, A} -> lists:flatten(io_lib:format("~p:~p/~p", [M, F, A])) - end, - Message = els_utils:to_binary( - io_lib:format( - "Cannot find definition for function ~s", - [Function] - ) - ), +-spec update_cache(true | {missing, function | module}, els_poi:poi_id(), map()) -> map(). +update_cache({missing, module}, {M, _F, _A}, Cache) -> + %% Cache missing module to avoid repeated lookups + Cache#{M => missing}; +update_cache(HasDef, Id, Cache) -> + Cache#{Id => HasDef}. + +-spec find_in_cache(els_poi:poi_id(), map()) -> _. +find_in_cache({M, _F, _A}, Cache) when is_map_key(M, Cache) -> + {ok, {missing, module}}; +find_in_cache(Id, Cache) -> + maps:find(Id, Cache). + +-spec kinds() -> [els_poi:poi_kind()]. +kinds() -> + [ + application, + implicit_fun, + import_entry, + export_entry, + nifs_entry + ]. + +-spec make_diagnostic(_, els_poi:poi()) -> [els_diagnostics:diagnostic()]. +make_diagnostic({missing, Kind}, #{id := Id} = POI) -> + Message = error_msg(Kind, Id), Severity = ?DIAGNOSTIC_ERROR, - els_diagnostics:make_diagnostic( - els_protocol:range(Range), - Message, - Severity, - source() - ). - --spec has_definition(els_poi:poi(), els_dt_document:item()) -> boolean(). -has_definition( - #{ - kind := application, - id := {module_info, 0} - }, - _ -) -> + [ + els_diagnostics:make_diagnostic( + els_protocol:range(range(Kind, POI)), + Message, + Severity, + source() + ) + ]; +make_diagnostic(true, _) -> + []. + +-spec range(module | function, els_poi:poi()) -> els_poi:poi_range(). +range(module, #{data := #{mod_range := Range}}) -> + Range; +range(function, #{data := #{name_range := Range}}) -> + Range; +range(_, #{range := Range}) -> + Range. + +-spec error_msg(module | function, els_poi:poi_id()) -> binary(). +error_msg(module, {M, _F, _A}) -> + els_utils:to_binary(io_lib:format("Cannot find module ~p", [M])); +error_msg(function, Id) -> + els_utils:to_binary(io_lib:format("Cannot find definition for function ~s", [id_str(Id)])). + +-spec id_str(els_poi:poi_id()) -> string(). +id_str(Id) -> + case Id of + {F, A} -> lists:flatten(io_lib:format("~p/~p", [F, A])); + {M, F, A} -> lists:flatten(io_lib:format("~p:~p/~p", [M, F, A])) + end. + +-spec has_definition(els_poi:poi(), els_dt_document:item(), _) -> + true | {missing, function | module}. +has_definition(#{data := #{imported := true}}, _Document, _Opts) -> + %% Call to a bif true; -has_definition( - #{ - kind := application, - id := {module_info, 1} - }, - _ -) -> +has_definition(#{id := {module_info, 0}}, _, _) -> true; -has_definition( - #{ - kind := application, - data := #{mod_is_variable := true} - }, - _ -) -> +has_definition(#{id := {module_info, 1}}, _, _) -> true; -has_definition( - #{ - kind := application, - id := {Module, module_info, Arity} - }, - _ -) when Arity =:= 0; Arity =:= 1 -> - {ok, []} =/= els_dt_document_index:lookup(Module); -has_definition( - #{ - kind := application, - id := {record_info, 2} - }, - _ -) -> +has_definition(#{data := #{mod_is_variable := true}}, _, _) -> true; -has_definition( - #{ - kind := application, - id := {behaviour_info, 1} - }, - _ -) -> +has_definition(#{data := #{fun_is_variable := true}}, _, _) -> true; -has_definition( - #{ - kind := application, - data := #{fun_is_variable := true} - }, - _ -) -> +has_definition(#{id := {Module, module_info, Arity}}, _, _) when Arity =:= 0; Arity =:= 1 -> + case els_dt_document_index:lookup(Module) of + {ok, []} -> + {missing, module}; + {ok, _} -> + true + end; +has_definition(#{id := {record_info, 2}}, _, _) -> + true; +has_definition(#{id := {behaviour_info, 1}}, _, _) -> + true; +has_definition(#{id := {lager, Level, Arity}}, _, _) -> + lager_definition(Level, Arity); +has_definition(#{id := {lists, append, 1}}, _, _) -> + %% lists:append/1 isn't indexed for some reason true; has_definition( + #{id := {F, A}} = POI, + Document, #{ - kind := application, - id := {lager, Level, Arity} - }, - _ + %% Compiler already checks local function calls + compiler_enabled := false + } ) -> - lager_definition(Level, Arity); -has_definition(POI, #{uri := Uri}) -> - case els_code_navigation:goto_definition(Uri, POI) of - {ok, _Defs} -> + Uri = els_dt_document:uri(Document), + MFA = {els_uri:module(Uri), F, A}, + case function_lookup(MFA) of + true -> + true; + false -> + case els_code_navigation:goto_definition(Uri, POI) of + {ok, _Defs} -> + true; + {error, _Error} -> + {missing, function} + end + end; +has_definition(#{id := {M, _F, _A} = MFA} = POI, _Document, _Opts) -> + case function_lookup(MFA) of + true -> true; - {error, _Error} -> - false + false -> + case els_utils:find_module(M) of + {ok, Uri} -> + case els_code_navigation:goto_definition(Uri, POI) of + {ok, _Defs} -> + true; + {error, _Error} -> + {missing, function} + end; + {error, _} -> + {missing, module} + end + end; +has_definition(_POI, #{uri := _Uri}, _Opts) -> + true. + +-spec function_lookup(mfa()) -> boolean(). +function_lookup(MFA) -> + case els_db:lookup(els_dt_functions:name(), MFA) of + {ok, []} -> + false; + {ok, _} -> + true end. -spec lager_definition(atom(), integer()) -> boolean(). lager_definition(Level, Arity) when Arity =:= 1 orelse Arity =:= 2 -> - lists:member(Level, lager_levels()); + case lists:member(Level, lager_levels()) of + true -> + true; + false -> + {missing, function} + end; lager_definition(_, _) -> - false. + {missing, function}. -spec lager_levels() -> [atom()]. lager_levels() -> - [debug, info, notice, warning, error, critical, alert, emergency]. + [debug, debug_unsafe, info, notice, warning, error, critical, alert, emergency]. diff --git a/apps/els_lsp/src/els_db.erl b/apps/els_lsp/src/els_db.erl index 0d00bab3..a0fca420 100644 --- a/apps/els_lsp/src/els_db.erl +++ b/apps/els_lsp/src/els_db.erl @@ -32,6 +32,7 @@ tables() -> els_dt_document_index, els_dt_references, els_dt_signatures, + els_dt_functions, els_docs_memo ]. diff --git a/apps/els_lsp/src/els_diagnostics.erl b/apps/els_lsp/src/els_diagnostics.erl index 8cfb101a..96cb5898 100644 --- a/apps/els_lsp/src/els_diagnostics.erl +++ b/apps/els_lsp/src/els_diagnostics.erl @@ -116,11 +116,49 @@ make_diagnostic(Range, Message, Severity, Source, Data) -> -spec run_diagnostics(uri()) -> [pid()]. run_diagnostics(Uri) -> - [run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()]. + case is_initial_indexing_done() of + true -> + ok = wait_for_indexing_job(Uri), + [run_diagnostic(Uri, Id) || Id <- enabled_diagnostics()]; + false -> + ?LOG_INFO( + "Initial indexing is not done, skip running diagnostics for ~p", + [els_uri:module(Uri)] + ), + [] + end. %%============================================================================== %% Internal Functions %%============================================================================== +-spec is_initial_indexing_done() -> boolean(). +is_initial_indexing_done() -> + %% Keep in sync with els_indexing + Jobs = [<<"Applications">>, <<"OTP">>, <<"Dependencies">>], + JobTitles = els_background_job:list_titles(), + lists:all( + fun(Job) -> + not lists:member( + <<"Indexing ", Job/binary>>, + JobTitles + ) + end, + Jobs + ). + +-spec wait_for_indexing_job(uri()) -> ok. +wait_for_indexing_job(Uri) -> + %% Add delay to allowing indexing job to start + timer:sleep(10), + JobTitles = els_background_job:list_titles(), + case lists:member(<<"Indexing ", Uri/binary>>, JobTitles) of + false -> + %% No indexing job is running, we're ready! + ok; + true -> + %% Indexing job is still running, retry until it finishes + wait_for_indexing_job(Uri) + end. -spec run_diagnostic(uri(), diagnostic_id()) -> pid(). run_diagnostic(Uri, Id) -> diff --git a/apps/els_lsp/src/els_dt_functions.erl b/apps/els_lsp/src/els_dt_functions.erl new file mode 100644 index 00000000..8b7b955e --- /dev/null +++ b/apps/els_lsp/src/els_dt_functions.erl @@ -0,0 +1,134 @@ +%%============================================================================== +%% The 'functions' table +%%============================================================================== +-module(els_dt_functions). + +%%============================================================================== +%% Behaviour els_db_table +%%============================================================================== + +-behaviour(els_db_table). +-export([ + name/0, + opts/0 +]). + +%%============================================================================== +%% API +%%============================================================================== + +-export([ + insert/1, + versioned_insert/1, + lookup/1, + delete_by_uri/1, + versioned_delete_by_uri/2 +]). + +%%============================================================================== +%% Includes +%%============================================================================== +-include("els_lsp.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). + +%%============================================================================== +%% Item Definition +%%============================================================================== + +-record(els_dt_functions, { + mfa :: mfa() | '_' | {atom(), '_', '_'}, + version :: version() | '_' +}). +-type els_dt_functions() :: #els_dt_functions{}. +-type version() :: null | integer(). +-type item() :: #{ + mfa := mfa(), + version := version() +}. +-export_type([item/0]). + +%%============================================================================== +%% Callbacks for the els_db_table Behaviour +%%============================================================================== + +-spec name() -> atom(). +name() -> ?MODULE. + +-spec opts() -> proplists:proplist(). +opts() -> + [set]. + +%%============================================================================== +%% API +%%============================================================================== + +-spec from_item(item()) -> els_dt_functions(). +from_item(#{ + mfa := MFA, + version := Version +}) -> + #els_dt_functions{ + mfa = MFA, + version = Version + }. + +-spec to_item(els_dt_functions()) -> item(). +to_item(#els_dt_functions{ + mfa = MFA, + version = Version +}) -> + #{ + mfa => MFA, + version => Version + }. + +-spec insert(item()) -> ok | {error, any()}. +insert(Map) when is_map(Map) -> + Record = from_item(Map), + els_db:write(name(), Record). + +-spec versioned_insert(item()) -> ok | {error, any()}. +versioned_insert(#{mfa := MFA, version := Version} = Map) -> + Record = from_item(Map), + Condition = fun(#els_dt_functions{version = CurrentVersion}) -> + CurrentVersion =:= null orelse Version >= CurrentVersion + end, + els_db:conditional_write(name(), MFA, Record, Condition). + +-spec lookup(mfa()) -> {ok, [item()]}. +lookup({M, _F, _A} = MFA) -> + {ok, _Uris} = els_utils:find_modules(M), + {ok, Items} = els_db:lookup(name(), MFA), + {ok, [to_item(Item) || Item <- Items]}. + +-spec delete_by_uri(uri()) -> ok. +delete_by_uri(Uri) -> + case filename:extension(Uri) of + <<".erl">> -> + Module = els_uri:module(Uri), + Pattern = #els_dt_functions{mfa = {Module, '_', '_'}, _ = '_'}, + ok = els_db:match_delete(name(), Pattern); + _ -> + ok + end. + +-spec versioned_delete_by_uri(uri(), version()) -> ok. +versioned_delete_by_uri(Uri, Version) -> + case filename:extension(Uri) of + <<".erl">> -> + Module = els_uri:module(Uri), + MS = ets:fun2ms( + fun + (#els_dt_functions{mfa = {M, _, _}, version = CurrentVersion}) when + M =:= Module, + CurrentVersion =:= null orelse CurrentVersion < Version + -> + true; + (_) -> + false + end + ), + ok = els_db:select_delete(name(), MS); + _ -> + ok + end. diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl index cf793027..99eca4ee 100644 --- a/apps/els_lsp/src/els_indexing.erl +++ b/apps/els_lsp/src/els_indexing.erl @@ -101,6 +101,7 @@ deep_index(Document0, UpdateWords) -> end, case els_dt_document:versioned_insert(Document) of ok -> + index_functions(Id, Uri, POIs, Version), index_signatures(Id, Uri, Text, POIs, Version), case Source of otp -> @@ -133,6 +134,19 @@ index_signature(M, Text, #{id := {F, A}, range := Range, data := #{args := Args} args => Args }). +-spec index_functions(atom(), uri(), [els_poi:poi()], version()) -> ok. +index_functions(M, Uri, POIs, Version) -> + ok = els_dt_functions:versioned_delete_by_uri(Uri, Version), + [index_function(M, POI, Version) || #{kind := function} = POI <- POIs], + ok. + +-spec index_function(atom(), els_poi:poi(), version()) -> ok. +index_function(M, #{id := {F, A}}, Version) -> + els_dt_functions:versioned_insert(#{ + mfa => {M, F, A}, + version => Version + }). + -spec index_references(atom(), uri(), [els_poi:poi()], version()) -> ok. index_references(Id, Uri, POIs, Version) -> ok = els_dt_references:versioned_delete_by_uri(Uri, Version), @@ -249,8 +263,8 @@ start(Group, Skip, SkipTag, Entries, Source) -> }, ?LOG_INFO( "Completed indexing for ~s " - "(succeeded: ~p, skipped: ~p, failed: ~p)", - [Group, Succeeded, Skipped, Failed] + "(succeeded: ~p, skipped: ~p, failed: ~p, duration: ~p ms)", + [Group, Succeeded, Skipped, Failed, Duration] ), els_telemetry:send_notification(Event) end @@ -263,7 +277,8 @@ remove(Uri) -> ok = els_dt_document:delete(Uri), ok = els_dt_document_index:delete_by_uri(Uri), ok = els_dt_references:delete_by_uri(Uri), - ok = els_dt_signatures:delete_by_uri(Uri). + ok = els_dt_signatures:delete_by_uri(Uri), + ok = els_dt_functions:delete_by_uri(Uri). %%============================================================================== %% Internal functions diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl index 82ca6351..64f44925 100644 --- a/apps/els_lsp/test/els_diagnostics_SUITE.erl +++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl @@ -38,6 +38,7 @@ escript_warnings/1, escript_errors/1, crossref/1, + crossref_compiler_enabled/1, crossref_autoimport/1, crossref_autoimport_disabled/1, crossref_pseudo_functions/1, @@ -92,20 +93,22 @@ end_per_suite(Config) -> init_per_testcase(TestCase, Config) when TestCase =:= atom_typo -> - meck:new(els_atom_typo_diagnostics, [passthrough, no_link]), - meck:expect(els_atom_typo_diagnostics, is_default, 0, true), - els_mock_diagnostics:setup(), - els_test_utils:init_per_testcase(TestCase, Config); + init_with_diagnostics(TestCase, [<<"atom_typo">>], Config); init_per_testcase(TestCase, Config) when TestCase =:= crossref orelse TestCase =:= crossref_pseudo_functions orelse TestCase =:= crossref_autoimport orelse TestCase =:= crossref_autoimport_disabled -> - meck:new(els_crossref_diagnostics, [passthrough, no_link]), - meck:expect(els_crossref_diagnostics, is_default, 0, true), - els_mock_diagnostics:setup(), - els_test_utils:init_per_testcase(TestCase, Config); + init_with_diagnostics(TestCase, [<<"crossref">>], Config); +init_per_testcase(TestCase, Config) when + TestCase =:= elvis +-> + init_with_diagnostics(TestCase, [<<"elvis">>], Config); +init_per_testcase(TestCase, Config) when + TestCase =:= crossref_compiler_enabled +-> + init_with_diagnostics(TestCase, [<<"crossref">>, <<"compiler">>], Config); init_per_testcase(code_path_extra_dirs, Config) -> meck:new(yamerl, [passthrough, no_link]), Content = <<"code_path_extra_dirs:\n", " - \"../code_navigation/*/\"\n">>, @@ -129,11 +132,10 @@ init_per_testcase(use_long_names_custom_hostname, Config) -> <<"runtime:\n", " use_long_names: true\n", " cookie: mycookie\n", " node_name: my_node\n", " hostname: 127.0.0.1">>, init_long_names_config(Content, Config); -init_per_testcase(exclude_unused_includes = TestCase, Config) -> - els_mock_diagnostics:setup(), - NewConfig = els_test_utils:init_per_testcase(TestCase, Config), +init_per_testcase(exclude_unused_includes = TestCase, Config0) -> + Config = init_with_diagnostics(TestCase, [<<"unused_includes">>], Config0), els_config:set(exclude_unused_includes, ["et/include/et.hrl"]), - NewConfig; + Config; init_per_testcase(TestCase, Config) when TestCase =:= compiler_telemetry -> els_mock_diagnostics:setup(), mock_compiler_telemetry_enabled(), @@ -193,28 +195,17 @@ init_per_testcase(TestCase, Config) when -> mock_refactorerl(), els_test_utils:init_per_testcase(TestCase, Config); +init_per_testcase(TestCase, Config) when + TestCase =:= unused_includes; + TestCase =:= unused_includes_broken; + TestCase =:= unused_includes_compiler_attribute +-> + init_with_diagnostics(TestCase, [<<"unused_includes">>], Config); init_per_testcase(TestCase, Config) -> els_mock_diagnostics:setup(), els_test_utils:init_per_testcase(TestCase, Config). -spec end_per_testcase(atom(), config()) -> ok. -end_per_testcase(TestCase, Config) when - TestCase =:= atom_typo --> - meck:unload(els_atom_typo_diagnostics), - els_test_utils:end_per_testcase(TestCase, Config), - els_mock_diagnostics:teardown(), - ok; -end_per_testcase(TestCase, Config) when - TestCase =:= crossref orelse - TestCase =:= crossref_pseudo_functions orelse - TestCase =:= crossref_autoimport orelse - TestCase =:= crossref_autoimport_disabled --> - meck:unload(els_crossref_diagnostics), - els_test_utils:end_per_testcase(TestCase, Config), - els_mock_diagnostics:teardown(), - ok; end_per_testcase(TestCase, Config) when TestCase =:= code_path_extra_dirs orelse TestCase =:= use_long_names orelse @@ -226,6 +217,7 @@ end_per_testcase(TestCase, Config) when els_mock_diagnostics:teardown(), ok; end_per_testcase(exclude_unused_includes = TestCase, Config) -> + reset_diagnostics_config(Config), els_config:set(exclude_unused_includes, []), els_test_utils:end_per_testcase(TestCase, Config), els_mock_diagnostics:teardown(), @@ -262,6 +254,7 @@ end_per_testcase(TestCase, Config) when els_mock_diagnostics:teardown(), ok; end_per_testcase(TestCase, Config) -> + reset_diagnostics_config(Config), els_test_utils:end_per_testcase(TestCase, Config), els_mock_diagnostics:teardown(), ok. @@ -792,7 +785,23 @@ crossref(_Config) -> }, #{ message => <<"Cannot find definition for function lists:map/3">>, - range => {{5, 2}, {5, 11}} + range => {{5, 8}, {5, 11}} + } + ], + Warnings = [], + Hints = [], + els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints). + +-spec crossref_compiler_enabled(config()) -> ok. +crossref_compiler_enabled(_Config) -> + Path = src_path("diagnostics_xref.erl"), + Source = <<"CrossRef">>, + %% Don't expect diagnostics for missing local functions if compiler is enabled + Errors = + [ + #{ + message => <<"Cannot find definition for function lists:map/3">>, + range => {{5, 8}, {5, 11}} } ], Warnings = [], @@ -806,28 +815,16 @@ crossref_pseudo_functions(_Config) -> Errors = [ #{ - message => - << - "Cannot find definition for function " - "unknown_module:nonexistent/0" - >>, - range => {{34, 2}, {34, 28}} + message => <<"Cannot find module unknown_module">>, + range => {{36, 2}, {36, 16}} }, #{ - message => - << - "Cannot find definition for function " - "unknown_module:module_info/1" - >>, - range => {{13, 2}, {13, 28}} + message => <<"Cannot find module unknown_module">>, + range => {{13, 2}, {13, 16}} }, #{ - message => - << - "Cannot find definition for function " - "unknown_module:module_info/0" - >>, - range => {{12, 2}, {12, 28}} + message => <<"Cannot find module unknown_module">>, + range => {{12, 2}, {12, 16}} } ], els_test:run_diagnostics_test(Path, <<"CrossRef">>, Errors, [], []). @@ -846,7 +843,7 @@ crossref_autoimport_disabled(_Config) -> %% This testcase cannot be run from an Erlang source tree version, %% it needs a released version. Path = src_path("diagnostics_autoimport_disabled.erl"), - els_test:run_diagnostics_test(Path, <<"CrossRef">>, [], [], []). + els_test:run_diagnostics_test(Path, <<"Quick CrossRef">>, [], [], []). -spec unused_includes(config()) -> ok. unused_includes(_Config) -> @@ -1156,3 +1153,29 @@ mock_refactorerl() -> unmock_refactoerl() -> meck:unload(els_refactorerl_diagnostics), meck:unload(els_refactorerl_utils). + +-spec init_with_diagnostics(atom(), [binary()], config()) -> config(). +init_with_diagnostics(TestCase, Diags, Config0) -> + els_mock_diagnostics:setup(), + Config = els_test_utils:init_per_testcase(TestCase, Config0), + enable_diagnostics(Diags, Config). + +%% Enable given diagnostics and disable the rest +-spec enable_diagnostics([binary()], config()) -> config(). +enable_diagnostics(Diags, Config) -> + Available = els_diagnostics:available_diagnostics(), + OldDiagnosticsConfig = els_config:get(diagnostics), + Disabled = Available -- Diags, + DiagConfig = #{"enabled" => Diags, "disabled" => Disabled}, + els_config:set(diagnostics, DiagConfig), + [{old_diagnostics_config, OldDiagnosticsConfig} | Config]. + +-spec reset_diagnostics_config(config()) -> config(). +reset_diagnostics_config(Config) -> + case proplists:get_value(old_diagnostics_config, Config, undefined) of + undefined -> + Config; + DiagnosticsConfig -> + ok = els_config:set(diagnostics, DiagnosticsConfig), + Config + end.