From 1c9c875b5ca57bc2ae7225045a185bd259f77cff Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Tue, 31 May 2022 23:42:17 +0530 Subject: [PATCH 1/7] Feature: Store hank output in json format --- rebar.config | 3 +- src/rebar3_hank_prv.erl | 90 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index 53292d9..fee44dd 100644 --- a/rebar.config +++ b/rebar.config @@ -17,7 +17,8 @@ {rebar3_format, "~> 1.2.0"}, {rebar3_lint, "~> 1.0.2"}, {rebar3_sheldon, "~> 0.4.2"}, - {rebar3_ex_doc, "~> 0.2.9"}]}. + {rebar3_ex_doc, "~> 0.2.9"}, + {jsx, "~> 2.10"}]}. {dialyzer, [{warnings, [no_return, unmatched_returns, error_handling, underspecs]}]}. diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index 127795d..4f90e9c 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, + "Output Json File Name (default: empty string)"} + ]. %% @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), + write_data_to_json_file(Results, State), {error, format_results(Results)} catch Kind:Error:Stack -> @@ -120,6 +127,87 @@ format_result(#{file := File, text := Msg}) -> hank_utils:format_text("~ts:~tp: ~ts", [File, Line, Msg]). +-spec write_data_to_json_file([hank_rule:result()], rebar_state:t()) -> ok. +write_data_to_json_file(Result, State) -> + {Args, _} = rebar_state:command_parsed_args(State), + JsonFilePath = + case lists:keyfind(output_json_file, 1, Args) of + {output_json_file, Value} -> + Value; + _ -> + undefined + end, + case JsonFilePath of + undefined -> + ok; + FilePath -> + case valid_json_format(FilePath) of + true -> + ConvertedResult = convert_data_to_binary(Result), + EncodedResult = jsx:encode(ConvertedResult), + ok = file:write_file(FilePath, EncodedResult); + false -> + ok + end + end. + +-spec valid_json_format(string()) -> boolean(). +valid_json_format(JsonFilePath) -> + JsonFileName = lists:last(string:tokens(JsonFilePath, "/")), + case lists:last(string:tokens(JsonFileName, ".")) of + "json" -> + true; + _ -> + false + end. + +-spec convert_data_to_binary([hank_rule:result()]) -> list(). +convert_data_to_binary(Data) -> + Func = + fun(RuleDetailMap) -> + #{file := FileName, line := Line, rule := RuleBroken, text := Description} = RuleDetailMap, + #{ + <<"path">> => to_binary(FileName), + <<"start_line">> => Line, + <<"hank_rule_broken">> => to_binary(RuleBroken), + <<"title">> => compute_title(RuleBroken), + <<"message">> => to_binary(Description)} + end, + [Func(RuleDetails) || RuleDetails <- Data]. + +-spec compute_title(atom()) -> binary(). +compute_title(RuleBroken) -> + case RuleBroken of + unused_macros -> + <<"Unused Macros">>; + single_use_hrl_attrs -> + <<"Macro is only used once">>; + unused_record_fields -> + <<"Field in the record is unused">>; + unused_hrls -> + <<"Unused hrl files">>; + unused_configuration_options -> + <<"Unused config">>; + unused_callbacks -> + <<"Unused callback functions">>; + unnecessary_function_arguments -> + <<"Unused function arguments found">>; + single_use_hrls -> + <<"Hrl is only used once">> + end. + +to_binary(Input) when is_atom(Input) -> + atom_to_binary(Input, utf8); +to_binary(Input) when is_integer(Input) -> + integer_to_binary(Input); +to_binary(Input) when is_float(Input) -> + float_to_binary(Input, [{decimals, 10}, compact]); +to_binary(Input) when is_list(Input) -> + list_to_binary(Input); +to_binary(Input) when is_pid(Input) -> + list_to_binary(pid_to_list(Input)); +to_binary(Input) -> Input. + %% @private %% @doc Determines files that should be fully hidden to Hank. is_hidden(Filename) -> From 3666eb7fdf05a367b68c588ff995324a04ea5152 Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Sun, 5 Jun 2022 22:35:31 +0530 Subject: [PATCH 2/7] Code review changes --- rebar.config | 2 +- src/rebar3_hank_prv.erl | 21 +++++++-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/rebar.config b/rebar.config index fee44dd..b6567f4 100644 --- a/rebar.config +++ b/rebar.config @@ -18,7 +18,7 @@ {rebar3_lint, "~> 1.0.2"}, {rebar3_sheldon, "~> 0.4.2"}, {rebar3_ex_doc, "~> 0.2.9"}, - {jsx, "~> 2.10"}]}. + {jsx, "~> 3.1.0"}]}. {dialyzer, [{warnings, [no_return, unmatched_returns, error_handling, underspecs]}]}. diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index 4f90e9c..35787f8 100644 --- a/src/rebar3_hank_prv.erl +++ b/src/rebar3_hank_prv.erl @@ -130,25 +130,18 @@ format_result(#{file := File, -spec write_data_to_json_file([hank_rule:result()], rebar_state:t()) -> ok. write_data_to_json_file(Result, State) -> {Args, _} = rebar_state:command_parsed_args(State), - JsonFilePath = - case lists:keyfind(output_json_file, 1, Args) of - {output_json_file, Value} -> - Value; - _ -> - undefined - end, - case JsonFilePath of - undefined -> - ok; - FilePath -> - case valid_json_format(FilePath) of + case lists:keyfind(output_json_file, 1, Args) of + {output_json_file, JsonFilePath} -> + case valid_json_format(JsonFilePath) of true -> ConvertedResult = convert_data_to_binary(Result), EncodedResult = jsx:encode(ConvertedResult), - ok = file:write_file(FilePath, EncodedResult); + ok = file:write_file(JsonFilePath, EncodedResult); false -> ok - end + end; + _ -> + ok end. -spec valid_json_format(string()) -> boolean(). From 1f547d8a9bfedbb36b3b853db3246583868fad37 Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Sun, 5 Jun 2022 22:40:18 +0530 Subject: [PATCH 3/7] CR changes --- src/rebar3_hank_prv.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index 35787f8..d563815 100644 --- a/src/rebar3_hank_prv.erl +++ b/src/rebar3_hank_prv.erl @@ -75,7 +75,7 @@ do(State) -> unused_ignores := UnusedIgnores, stats := Stats} -> instrument(Stats, UnusedIgnores, State), - write_data_to_json_file(Results, State), + maybe_write_data_to_json_file(Results, State), {error, format_results(Results)} catch Kind:Error:Stack -> @@ -127,8 +127,8 @@ format_result(#{file := File, text := Msg}) -> hank_utils:format_text("~ts:~tp: ~ts", [File, Line, Msg]). --spec write_data_to_json_file([hank_rule:result()], rebar_state:t()) -> ok. -write_data_to_json_file(Result, State) -> +-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), case lists:keyfind(output_json_file, 1, Args) of {output_json_file, JsonFilePath} -> From 8b542bda538ddae32bed1e27191367dfabbafce3 Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Wed, 8 Jun 2022 13:34:59 +0530 Subject: [PATCH 4/7] CR changes --- README.md | 8 ++++- src/hank_rule.erl | 33 +++++++++++++++++++ src/rebar3_hank_prv.erl | 72 ++++------------------------------------- 3 files changed, 47 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 2902abe..0730255 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 issue on the console. But you can save this result 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/src/hank_rule.erl b/src/hank_rule.erl index 18f2a8b..ab5b2e8 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,35 @@ 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">> => Description + }. + +-spec compute_title(atom()) -> binary(). +compute_title(RuleBroken) -> + case RuleBroken of + unused_macros -> + <<"Unused Macros">>; + single_use_hrl_attrs -> + <<"Macro is only used once">>; + unused_record_fields -> + <<"Field in the record is unused">>; + unused_hrls -> + <<"Unused hrl files">>; + unused_configuration_options -> + <<"Unused config">>; + unused_callbacks -> + <<"Unused callback functions">>; + unnecessary_function_arguments -> + <<"Unused function arguments found">>; + single_use_hrls -> + <<"Hrl is only used once">> + end. \ No newline at end of file diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index d563815..d404814 100644 --- a/src/rebar3_hank_prv.erl +++ b/src/rebar3_hank_prv.erl @@ -40,7 +40,7 @@ opts() -> $o, "output_json_file", string, - "Output Json File Name (default: empty string)"} + "Emit output (in JSON format) to a file (default: empty string - meaning: donot emit output"} ]. %% @private @@ -132,74 +132,16 @@ maybe_write_data_to_json_file(Result, State) -> {Args, _} = rebar_state:command_parsed_args(State), case lists:keyfind(output_json_file, 1, Args) of {output_json_file, JsonFilePath} -> - case valid_json_format(JsonFilePath) of - true -> - ConvertedResult = convert_data_to_binary(Result), - EncodedResult = jsx:encode(ConvertedResult), - ok = file:write_file(JsonFilePath, EncodedResult); - false -> - ok - end; + ConvertedResult = convert_data_to_binary(Result), + EncodedResult = jsx:encode(ConvertedResult), + ok = file:write_file(JsonFilePath, EncodedResult); _ -> ok end. --spec valid_json_format(string()) -> boolean(). -valid_json_format(JsonFilePath) -> - JsonFileName = lists:last(string:tokens(JsonFilePath, "/")), - case lists:last(string:tokens(JsonFileName, ".")) of - "json" -> - true; - _ -> - false - end. - --spec convert_data_to_binary([hank_rule:result()]) -> list(). -convert_data_to_binary(Data) -> - Func = - fun(RuleDetailMap) -> - #{file := FileName, line := Line, rule := RuleBroken, text := Description} = RuleDetailMap, - #{ - <<"path">> => to_binary(FileName), - <<"start_line">> => Line, - <<"hank_rule_broken">> => to_binary(RuleBroken), - <<"title">> => compute_title(RuleBroken), - <<"message">> => to_binary(Description)} - end, - [Func(RuleDetails) || RuleDetails <- Data]. - --spec compute_title(atom()) -> binary(). -compute_title(RuleBroken) -> - case RuleBroken of - unused_macros -> - <<"Unused Macros">>; - single_use_hrl_attrs -> - <<"Macro is only used once">>; - unused_record_fields -> - <<"Field in the record is unused">>; - unused_hrls -> - <<"Unused hrl files">>; - unused_configuration_options -> - <<"Unused config">>; - unused_callbacks -> - <<"Unused callback functions">>; - unnecessary_function_arguments -> - <<"Unused function arguments found">>; - single_use_hrls -> - <<"Hrl is only used once">> - end. - -to_binary(Input) when is_atom(Input) -> - atom_to_binary(Input, utf8); -to_binary(Input) when is_integer(Input) -> - integer_to_binary(Input); -to_binary(Input) when is_float(Input) -> - float_to_binary(Input, [{decimals, 10}, compact]); -to_binary(Input) when is_list(Input) -> - list_to_binary(Input); -to_binary(Input) when is_pid(Input) -> - list_to_binary(pid_to_list(Input)); -to_binary(Input) -> Input. +-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. From 686c17b9e1541c7827dd1d693c0c9b6fddde625b Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Wed, 8 Jun 2022 13:48:25 +0530 Subject: [PATCH 5/7] Minor change --- src/hank_rule.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hank_rule.erl b/src/hank_rule.erl index ab5b2e8..3b1d66d 100644 --- a/src/hank_rule.erl +++ b/src/hank_rule.erl @@ -57,7 +57,7 @@ result_to_json(Data) -> <<"start_line">> => Line, <<"hank_rule_broken">> => atom_to_binary(RuleBroken), <<"title">> => compute_title(RuleBroken), - <<"message">> => Description + <<"message">> => iolist_to_binary(Description) }. -spec compute_title(atom()) -> binary(). From 6569555c942af65e04c5a53128047380d8fc4d38 Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Wed, 8 Jun 2022 19:44:41 +0530 Subject: [PATCH 6/7] Option so that json file can be put under hank in rebar.config --- src/rebar3_hank_prv.erl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index d404814..1d324e0 100644 --- a/src/rebar3_hank_prv.erl +++ b/src/rebar3_hank_prv.erl @@ -130,13 +130,19 @@ format_result(#{file := File, -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} -> - ConvertedResult = convert_data_to_binary(Result), - EncodedResult = jsx:encode(ConvertedResult), ok = file:write_file(JsonFilePath, EncodedResult); _ -> - ok + 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(). From e5293292fba3a299b00e46af02ff481babc4a092 Mon Sep 17 00:00:00 2001 From: Naman Gupta Date: Wed, 8 Jun 2022 20:06:12 +0530 Subject: [PATCH 7/7] Code review changes --- README.md | 2 +- src/hank_rule.erl | 24 +++++++++++------------- src/rebar3_hank_prv.erl | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0730255..25d7976 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ By default, Hank will emit warnings such as the following ones if you are ignori 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 issue on the console. But you can save this result in JSON format, +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 diff --git a/src/hank_rule.erl b/src/hank_rule.erl index 3b1d66d..d6ccccf 100644 --- a/src/hank_rule.erl +++ b/src/hank_rule.erl @@ -51,14 +51,12 @@ is_ignored(Rule, 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) - }. + #{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) -> @@ -66,9 +64,9 @@ compute_title(RuleBroken) -> unused_macros -> <<"Unused Macros">>; single_use_hrl_attrs -> - <<"Macro is only used once">>; + <<"Macro only used once">>; unused_record_fields -> - <<"Field in the record is unused">>; + <<"Unused field in record">>; unused_hrls -> <<"Unused hrl files">>; unused_configuration_options -> @@ -76,7 +74,7 @@ compute_title(RuleBroken) -> unused_callbacks -> <<"Unused callback functions">>; unnecessary_function_arguments -> - <<"Unused function arguments found">>; + <<"Unused function arguments">>; single_use_hrls -> - <<"Hrl is only used once">> - end. \ No newline at end of file + <<"Hrl file only used once">> + end. diff --git a/src/rebar3_hank_prv.erl b/src/rebar3_hank_prv.erl index 1d324e0..b619372 100644 --- a/src/rebar3_hank_prv.erl +++ b/src/rebar3_hank_prv.erl @@ -40,7 +40,7 @@ opts() -> $o, "output_json_file", string, - "Emit output (in JSON format) to a file (default: empty string - meaning: donot emit output"} + "Emit output (in JSON format) to a file (default: empty string, meaning: do not emit output"} ]. %% @private