diff --git a/README.md b/README.md index 2902abe..25d7976 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,19 @@ $ rebar3 help hank Usage: rebar3 hank [-u ] - -u, --unused_ignores Warn on unused ignores (default: true). + Usage: rebar3 hank [-u ] [-o ] + + -u, --unused_ignores Warn on unused ignores (default: true). + -o, --output_json_file Emit output (in JSON format) to a file (default: empty string - meaning: donot emit output ``` By default, Hank will emit warnings such as the following ones if you are ignoring rules that you don't need to ignore (more on that below). But you can turn those warnings off, by using `--unused_ignores=no`. It's worth noting that, even when those warnings are printed, that doesn't affect the overall result of the command. That is, if Hank can't find any instances of oxbow code, it will return successfully (i.e. `exit code: 0`) even when it may print these warnings. +By default, if Hank detect issues in your code, it will print those issues on the console. But you can save this result in a file (in JSON format), +by using `--output_json_file=output_filename.json`. + ```markdown ===> The following ignore specs are no longer needed and can be removed: * src/ignore_not_empty.erl: unused_hrls diff --git a/rebar.config b/rebar.config index c05937f..0e873a6 100644 --- a/rebar.config +++ b/rebar.config @@ -17,7 +17,8 @@ {rebar3_format, "~> 1.2.0"}, {rebar3_lint, "~> 1.1.0"}, {rebar3_sheldon, "~> 0.4.2"}, - {rebar3_ex_doc, "~> 0.2.11"}]}. + {rebar3_ex_doc, "~> 0.2.11"}, + {jsx, "~> 3.1.0"}]}. {dialyzer, [{warnings, [no_return, unmatched_returns, error_handling, underspecs]}]}. diff --git a/src/hank_rule.erl b/src/hank_rule.erl index 18f2a8b..d6ccccf 100644 --- a/src/hank_rule.erl +++ b/src/hank_rule.erl @@ -20,6 +20,7 @@ -export([default_rules/0]). -export([analyze/3]). -export([is_ignored/3]). +-export([result_to_json/1]). %% @doc The list of default rules to apply -spec default_rules() -> []. @@ -47,3 +48,33 @@ analyze(Rule, ASTs, Context) -> -spec is_ignored(t(), ignore_pattern(), all | term()) -> boolean(). is_ignored(Rule, Pattern, IgnoreSpec) -> IgnoreSpec =:= all orelse Rule:ignored(Pattern, IgnoreSpec). + +-spec result_to_json(hank_rule:result()) -> map(). +result_to_json(Data) -> + #{file := FileName, line := Line, rule := RuleBroken, text := Description} = Data, + #{<<"path">> => iolist_to_binary(FileName), + <<"start_line">> => Line, + <<"hank_rule_broken">> => atom_to_binary(RuleBroken), + <<"title">> => compute_title(RuleBroken), + <<"message">> => iolist_to_binary(Description)}. + +-spec compute_title(atom()) -> binary(). +compute_title(RuleBroken) -> + case RuleBroken of + unused_macros -> + <<"Unused Macros">>; + single_use_hrl_attrs -> + <<"Macro only used once">>; + unused_record_fields -> + <<"Unused field in record">>; + unused_hrls -> + <<"Unused hrl files">>; + unused_configuration_options -> + <<"Unused config">>; + unused_callbacks -> + <<"Unused callback functions">>; + unnecessary_function_arguments -> + <<"Unused function arguments">>; + single_use_hrls -> + <<"Hrl file only used once">> + end. diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index 127795d..b619372 100644 --- a/src/rebar3_hank_prv.erl +++ b/src/rebar3_hank_prv.erl @@ -35,7 +35,13 @@ opts() -> $u, "unused_ignores", boolean, - "Warn on unused ignores (default: true)."}]. + "Warn on unused ignores (default: true)."}, + {output_json_file, + $o, + "output_json_file", + string, + "Emit output (in JSON format) to a file (default: empty string, meaning: do not emit output"} + ]. %% @private -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, iodata()}. @@ -69,6 +75,7 @@ do(State) -> unused_ignores := UnusedIgnores, stats := Stats} -> instrument(Stats, UnusedIgnores, State), + maybe_write_data_to_json_file(Results, State), {error, format_results(Results)} catch Kind:Error:Stack -> @@ -120,6 +127,28 @@ format_result(#{file := File, text := Msg}) -> hank_utils:format_text("~ts:~tp: ~ts", [File, Line, Msg]). +-spec maybe_write_data_to_json_file([hank_rule:result()], rebar_state:t()) -> ok. +maybe_write_data_to_json_file(Result, State) -> + {Args, _} = rebar_state:command_parsed_args(State), + ConvertedResult = convert_data_to_binary(Result), + EncodedResult = jsx:encode(ConvertedResult), + case lists:keyfind(output_json_file, 1, Args) of + {output_json_file, JsonFilePath} -> + ok = file:write_file(JsonFilePath, EncodedResult); + _ -> + JsonFilePassed = proplists:get_value(output_json_file, rebar_state:get(State, hank, []), []), + case JsonFilePassed of + [] -> + ok; + _ -> + ok = file:write_file(JsonFilePassed, EncodedResult) + end + end. + +-spec convert_data_to_binary([hank_rules:result()]) -> list(). +convert_data_to_binary(Results) -> + lists:map(fun hank_rule:result_to_json/1, Results). + %% @private %% @doc Determines files that should be fully hidden to Hank. is_hidden(Filename) ->