Skip to content
This repository was archived by the owner on Aug 15, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ doc
# Emacs temporary files
.#*
*#
rebar3
4 changes: 4 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/diagnostics_sheldon.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%% Trigger some "somestrange" warnings

%% Trigger some "sheldon" warnings
-module(diagnostics_sheldon).
14 changes: 13 additions & 1 deletion apps/els_lsp/src/els_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
-callback run(uri()) -> [diagnostic()].
-callback source() -> binary().
-callback on_complete(uri(), [diagnostic()]) -> ok.
-optional_callbacks([ on_complete/2 ]).
-callback init() -> ok.
-optional_callbacks([ on_complete/2, init/0 ]).

%%==============================================================================
%% API
Expand All @@ -63,6 +64,7 @@ available_diagnostics() ->
, <<"dialyzer">>
, <<"gradualizer">>
, <<"elvis">>
, <<"sheldon">>
, <<"unused_includes">>
, <<"unused_macros">>
, <<"unused_record_fields">>
Expand Down Expand Up @@ -116,6 +118,7 @@ run_diagnostic(Uri, Id) ->
els_diagnostics_provider:notify(Diagnostics, self())
end
},
ok = init_diagnostic(CbModule),
{ok, Pid} = els_background_job:new(Config),
Pid.

Expand Down Expand Up @@ -146,3 +149,12 @@ valid(Ids0) ->
})
end,
Valid.

-spec init_diagnostic(atom()) -> ok.
init_diagnostic(CbModule) ->
case erlang:function_exported(CbModule, init, 0) of
true ->
CbModule:init();
false ->
ok
end.
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_lsp.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
getopt,
erlfmt,
els_core,
gradualizer
gradualizer,
rebar3_sheldon
]},
{env, []},
{modules, []},
Expand Down
124 changes: 124 additions & 0 deletions apps/els_lsp/src/els_sheldon_diagnostics.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
%%==============================================================================
%% Sheldon diagnostics
%%==============================================================================

-module(els_sheldon_diagnostics).

%%==============================================================================
%% Behaviours
%%==============================================================================

-behaviour(els_diagnostics).

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

-export([ init/0
, is_default/0
, run/1
, source/0
]).

%%==============================================================================
%% Includes
%%==============================================================================

-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").

%%==============================================================================
%% Callback Functions
%%==============================================================================

-spec init() -> ok.
init() ->
%% By default "sheldon" is not started by reason that he spend few seconds
%% to prepare and load dictionary. The "sheldon" shoulld be load only once
%% when diagnostic is enabled.
application:ensure_all_started(sheldon),
ok.

-spec is_default() -> boolean().
is_default() ->
false.

-spec run(uri()) -> [els_diagnostics:diagnostic()].
run(Uri) ->
case els_utils:project_relative(Uri) of
{error, not_relative} -> [];
RelFile ->
try
RegEx = "[_@./#&+-=*]",
rebar3_sheldon_ast:spellcheck([RelFile], RegEx)
of
[] -> [];
Problems -> format_diagnostics(Problems)
catch Type:Error:Stacktrace ->
?LOG_WARNING( "Sheldon error: [type=~p] [error=~p] [stacktrace=~p]"
, [Type, Error, Stacktrace]
),
[]
end
end.

-spec source() -> binary().
source() ->
<<"Sheldon">>.

%%==============================================================================
%% Internal Functions
%%==============================================================================

-spec format_diagnostics(any()) -> [map()].
format_diagnostics(Data) ->
R = format_rules(Data),
lists:flatten(R).

-spec format_rules([any()]) -> [[map()]].
format_rules([]) ->
[];
format_rules([#{reason := #{misspelled_words := Miss}} = Data | EItems]) ->
ItemDiags = format_item(Miss, Data),
[ItemDiags | format_rules(EItems)].

-spec format_item([map()], map()) -> [map()].
format_item([#{candidates := [], word := Word} | Items], Data) ->
#{line := Line, type := Type} = Data,
Msg = format_text("The word ~p in ~p is unknown.", [Word, Type]),
Diagnostic = diagnostic(Msg, Line, ?DIAGNOSTIC_WARNING),
[Diagnostic | format_item(Items, Data)];
format_item([#{candidates := Candidates, word := Word} | Items], Data) ->
#{line := Line, type := Type} = Data,
FormatCandidates = format_sheldon_candidates(Candidates, []),
Text = "The word ~p in ~p is unknown. Maybe you wanted to use ~ts?",
Msg = format_text(Text, [Word, Type, FormatCandidates]),
Diagnostic = diagnostic(Msg, Line, ?DIAGNOSTIC_WARNING),
[Diagnostic | format_item(Items, Data)];
format_item([], _) ->
[].

-spec diagnostic( any(), integer(), els_diagnostics:severity()) -> map().
diagnostic(Msg, Ln, Severity) ->
Range = els_protocol:range(#{from => {Ln, 1}, to => {Ln + 1, 1}}),
Message = els_utils:to_binary(Msg),
#{ range => Range
, severity => Severity
, code => spellcheck
, source => source()
, message => Message
, relatedInformation => []
}.

-spec format_sheldon_candidates([any()], [[[any()] | char()]]) -> list().
format_sheldon_candidates([], Acc) ->
Acc;
format_sheldon_candidates([Candidate], Acc) ->
[Acc, format_text("~p", [Candidate])];
format_sheldon_candidates([Candidate | T], Acc) ->
format_sheldon_candidates(T, [Acc, format_text("~p or ", [Candidate])]).

-spec format_text(string(), list()) -> string().
format_text(Text, Args) ->
Formatted = io_lib:format(Text, Args),
unicode:characters_to_list(Formatted).
46 changes: 46 additions & 0 deletions apps/els_lsp/test/els_diagnostics_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
, unused_macros/1
, unused_record_fields/1
, gradualizer/1
, sheldon/1
]).

%%==============================================================================
Expand Down Expand Up @@ -123,6 +124,11 @@ init_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
meck:expect(els_gradualizer_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) when TestCase =:= sheldon ->
meck:new(els_sheldon_diagnostics, [passthrough, no_link]),
meck:expect(els_sheldon_diagnostics, is_default, 0, true),
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config);
init_per_testcase(TestCase, Config) ->
els_mock_diagnostics:setup(),
els_test_utils:init_per_testcase(TestCase, Config).
Expand Down Expand Up @@ -164,6 +170,11 @@ end_per_testcase(TestCase, Config) when TestCase =:= gradualizer ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
end_per_testcase(TestCase, Config) when TestCase =:= sheldon ->
meck:unload(els_sheldon_diagnostics),
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
ok;
end_per_testcase(TestCase, Config) ->
els_test_utils:end_per_testcase(TestCase, Config),
els_mock_diagnostics:teardown(),
Expand Down Expand Up @@ -654,6 +665,41 @@ gradualizer(_Config) ->
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints).

-spec sheldon(config()) -> ok.
sheldon(_Config) ->
case list_to_integer(erlang:system_info(otp_release)) >= 23 of
true ->
{ok, Cwd} = file:get_cwd(),
RootPath = els_test_utils:root_path(),
try
file:set_cwd(RootPath),
Path = src_path("diagnostics_sheldon.erl"),
Source = <<"Sheldon">>,
Errors = [],
Warnings = [ #{ code => spellcheck
, message => <<"The word \"sheldon\" in "
"comment is unknown. "
"Maybe you wanted to use \"Sheldon\"?">>
, range => {{2, 0}, {3, 0}}
, relatedInformation => []
}
, #{ code => spellcheck
, message => <<"The word \"somestrange\" in comment is "
"unknown.">>
, range => {{0, 0}, {1, 0}}
, relatedInformation => []
}
],
Hints = [],
els_test:run_diagnostics_test(Path, Source, Errors, Warnings, Hints)
catch _Err ->
file:set_cwd(Cwd)
end,
ok;
false ->
{skipped, "Sheldon diagnostics should run on OTP23+"}
end.

%%==============================================================================
%% Internal Functions
%%==============================================================================
Expand Down
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
, {tdiff, "0.1.2"}
, {uuid, "2.0.1", {pkg, uuid_erl}}
, {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {ref, "e93db1c"}}}
, {rebar3_sheldon, "0.4.2"}
]
}.

Expand Down
9 changes: 9 additions & 0 deletions rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
{<<"providers">>,{pkg,<<"providers">>,<<"1.8.1">>},1},
{<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.1">>},1},
{<<"rebar3_format">>,{pkg,<<"rebar3_format">>,<<"0.8.2">>},0},
{<<"rebar3_sheldon">>,{pkg,<<"rebar3_sheldon">>,<<"0.4.2">>},0},
{<<"redbug">>,{pkg,<<"redbug">>,<<"2.0.6">>},0},
{<<"sheldon">>,{pkg,<<"sheldon">>,<<"0.4.1">>},1},
{<<"tdiff">>,{pkg,<<"tdiff">>,<<"0.1.2">>},0},
{<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.1">>},0},
{<<"worker_pool">>,{pkg,<<"worker_pool">>,<<"5.1.0">>},2},
{<<"yamerl">>,{pkg,<<"yamerl">>,<<"0.8.1">>},0},
{<<"zipper">>,{pkg,<<"zipper">>,<<"1.0.1">>},1}]}.
[
Expand All @@ -32,9 +35,12 @@
{<<"providers">>, <<"70B4197869514344A8A60E2B2A4EF41CA03DEF43CFB1712ECF076A0F3C62F083">>},
{<<"quickrand">>, <<"6D861FA11E6EB51BB2343A2616EFF704C2681A9997F41ABC78E58FA76DA33981">>},
{<<"rebar3_format">>, <<"2D64DA61E0B87FCA6C4512ADA6D9CBC2B27ADC9AE6844178561147E7121761BD">>},
{<<"rebar3_sheldon">>, <<"DC69F6A5BA5B7AD8A547F656DF25DFE41E030DF2787467F5433A3210AE7FDB0C">>},
{<<"redbug">>, <<"A764690B012B67C404562F9C6E1BA47A73892EE17DF5C15F670B1A5BF9D2F25A">>},
{<<"sheldon">>, <<"1413143F9D96D30C6A18DD6746B5183F048B23AC345FF3BB92B399A5A69EBE93">>},
{<<"tdiff">>, <<"4E1B30321F1B3D600DF65CD60858EDE1235FE4E5EE042110AB5AD90CD6464AC5">>},
{<<"uuid">>, <<"1FD9079C544D521063897887A1C5B3302DCA98F9BB06AADCDC6FB0663F256797">>},
{<<"worker_pool">>, <<"61BA970F856AF8B2D85232ADC8E4B990FA1719F396FCDAD954702946395ADD80">>},
{<<"yamerl">>, <<"07DA13FFA1D8E13948943789665C62CCD679DFA7B324A4A2ED3149DF17F453A4">>},
{<<"zipper">>, <<"3CCB4F14B97C06B2749B93D8B6C204A1ECB6FAFC6050CACC3B93B9870C05952A">>}]},
{pkg_hash_ext,[
Expand All @@ -49,9 +55,12 @@
{<<"providers">>, <<"E45745ADE9C476A9A469EA0840E418AB19360DC44F01A233304E118A44486BA0">>},
{<<"quickrand">>, <<"14DB67D4AEF6B8815810EC9F3CCEF5E324B73B56CAE3687F99D752B85BDD4C96">>},
{<<"rebar3_format">>, <<"CA8FF27638C2169593D1449DACBE8895634193ED3334E906B54FC97F081F5213">>},
{<<"rebar3_sheldon">>, <<"19120875183E6EE7C11EC21FC99B12347A37F59386DB7A4759DBBA55EA621D4C">>},
{<<"redbug">>, <<"AAD9498671F4AB91EACA5099FE85A61618158A636E6286892C4F7CF4AF171D04">>},
{<<"sheldon">>, <<"15E24EDDCDB42FE07AA7913DA770DBCE4EE052A37AA76E467748D4C977F463C9">>},
{<<"tdiff">>, <<"E0C2E168F99252A5889768D5C8F1E6510A184592D4CFA06B22778A18D33D7875">>},
{<<"uuid">>, <<"AB57CACCD51F170011E5F444CE865F84B41605E483A9EFCC468C1AFAEC87553B">>},
{<<"worker_pool">>, <<"59A975728E5C2B69A23D1C5EBC3B63251EC4ACBF4E9D25F1C619E3AD4B550813">>},
{<<"yamerl">>, <<"96CB30F9D64344FED0EF8A92E9F16F207DE6C04DFFF4F366752CA79F5BCEB23F">>},
{<<"zipper">>, <<"6A1FD3E1F0CC1D1DF5642C9A0CE2178036411B0A5C9642851D1DA276BD737C2D">>}]}
].