rebar3 hank
command.
8 | Use it to find dead code in your applications.
9 |
10 | 10 | %% This rule assumes that hrl files will not be used outside your project. 11 | %% If you are writing a library that requires your clients to use some of 12 | %% your header files and attributes, you can add an ignore rule in 13 | %% rebar.config for it. 14 | %%15 | -module(single_use_hrl_attrs). 16 | 17 | -behaviour(hank_rule). 18 | 19 | -export([analyze/2, ignored/2]). 20 | 21 | %% @doc This builds a list of header files with its attributes. 22 | %% Then traverse the file ASTs mapping their macros and records 23 | %% And checks whether they were used just once. 24 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 25 | analyze(FilesAndASTs, _Context) -> 26 | HrlDefs = hrl_attrs(FilesAndASTs), 27 | AttributesUsed = lists:foldl(fun file_using/2, #{}, FilesAndASTs), 28 | [build_macro_result(HrlFile, MacroKey, AttributesUsed) 29 | || {HrlFile, #{define := Defines}} <- HrlDefs, 30 | MacroKey <- Defines, 31 | is_used_only_once(HrlFile, MacroKey, AttributesUsed)] 32 | ++ [build_record_result(HrlFile, RecordKey, AttributesUsed) 33 | || {HrlFile, #{record := Records}} <- HrlDefs, 34 | RecordKey <- Records, 35 | is_used_only_once(HrlFile, RecordKey, AttributesUsed)]. 36 | 37 | build_macro_result(HrlFile, {Macro, Line}, AttributesUsed) -> 38 | [File] = maps:get(Macro, AttributesUsed), 39 | Text = 40 | case Macro of 41 | {MacroName, none} -> 42 | hank_utils:format_text("?~ts is used only at ~ts", [MacroName, File]); 43 | {MacroName, MacroArity} -> 44 | hank_utils:format_text("?~ts/~tp is used only at ~ts", 45 | [MacroName, MacroArity, File]) 46 | end, 47 | #{file => HrlFile, 48 | line => Line, 49 | text => Text, 50 | pattern => Macro}. 51 | 52 | build_record_result(HrlFile, {Record, Line}, AttributesUsed) -> 53 | [File] = maps:get(Record, AttributesUsed), 54 | #{file => HrlFile, 55 | line => Line, 56 | text => hank_utils:format_text("#~tp is used only at ~ts", [Record, File]), 57 | pattern => Record}. 58 | 59 | is_used_only_once(HrlFile, {Key, _Line}, AttributesUsed) -> 60 | case maps:get(Key, AttributesUsed, []) of 61 | [SingleFile] -> 62 | %% There is nothing wrong with using an attribute only in the same 63 | %% file where it's defined. 64 | SingleFile /= HrlFile; 65 | _ -> 66 | false 67 | end. 68 | 69 | file_using({File, FileAST}, CurrentFiles) -> 70 | AddFun = fun(Files) -> lists:usort([File | Files]) end, 71 | FoldFun = 72 | fun(Node, Result) -> 73 | case erl_syntax:type(Node) of 74 | macro -> 75 | Key = macro_application_name(Node), 76 | maps:update_with(Key, AddFun, [File], Result); 77 | Attr 78 | when Attr =:= record_expr; 79 | Attr =:= record_access; 80 | Attr =:= record_index_expr; 81 | Attr =:= record_type -> 82 | Key = record_name(Node, Attr), 83 | maps:update_with(Key, AddFun, [File], Result); 84 | attribute -> 85 | case hank_utils:attr_name(Node) of 86 | ControlFlowAttr 87 | when ControlFlowAttr == ifdef; 88 | ControlFlowAttr == ifndef; 89 | ControlFlowAttr == undef -> 90 | Key = macro_control_flow_name(Node), 91 | maps:update_with(Key, AddFun, [File], Result); 92 | _ -> 93 | Result 94 | end; 95 | _ -> 96 | Result 97 | end 98 | end, 99 | erl_syntax_lib:fold(FoldFun, CurrentFiles, erl_syntax:form_list(FileAST)). 100 | 101 | %% @doc It collects the hrl attrs like {file, [attrs]} 102 | hrl_attrs(FilesAndASTs) -> 103 | [{File, attrs(AST)} || {File, AST} <- FilesAndASTs, filename:extension(File) == ".hrl"]. 104 | 105 | %% @doc A map with #{define => [], record => []} for each hrl tree 106 | attrs(AST) -> 107 | FoldFun = 108 | fun(Node, #{define := Defines, record := Records} = Acc) -> 109 | case erl_syntax:type(Node) of 110 | attribute -> 111 | case hank_utils:attr_name(Node) of 112 | define -> 113 | maps:put(define, 114 | [{hank_utils:macro_definition_name(Node), line(Node)} 115 | | Defines], 116 | Acc); 117 | record -> 118 | maps:put(record, 119 | [{record_definition_name(Node), line(Node)} | Records], 120 | Acc); 121 | _ -> 122 | Acc 123 | end; 124 | _ -> 125 | Acc 126 | end 127 | end, 128 | erl_syntax_lib:fold(FoldFun, #{define => [], record => []}, erl_syntax:form_list(AST)). 129 | 130 | macro_control_flow_name(Node) -> 131 | macro_application_name(hank_utils:macro_from_control_flow_attr(Node)). 132 | 133 | macro_application_name(Node) -> 134 | {hank_utils:macro_name(Node), hank_utils:macro_arity(Node)}. 135 | 136 | record_definition_name(Node) -> 137 | try erl_syntax_lib:analyze_record_attribute(Node) of 138 | {RecordName, _} -> 139 | RecordName 140 | catch 141 | _:syntax_error -> 142 | %% There is a macro in the record definition 143 | "" 144 | end. 145 | 146 | record_name(Node, Type) -> 147 | RecordName = 148 | case Type of 149 | record_expr -> 150 | erl_syntax:record_expr_type(Node); 151 | record_index_expr -> 152 | erl_syntax:record_index_expr_type(Node); 153 | record_access -> 154 | erl_syntax:record_access_type(Node); 155 | record_type -> 156 | erl_syntax:record_type_name(Node) 157 | end, 158 | erl_syntax:atom_value(RecordName). 159 | 160 | line(Node) -> 161 | hank_utils:node_line(Node). 162 | 163 | %% @doc Rule ignore specifications. Example: 164 | %%
165 | %% -hank([{single_use_hrl_attrs, 166 | %% ["ALL", %% Will ignore ?ALL, ?ALL() and ?ALL(X) 167 | %% {"ZERO", 0}, %% Will ignore ?ZERO() but not ?ZERO(X) nor ?ZERO 168 | %% {"ONE", 1}, %% Will ignore ?ONE(X) but not ?ONE() nor ?ONE 169 | %% {"NONE", none}, %% Will ignore ?NONE but not ?NONE(X) nor ?NONE() 170 | %% record_name %% Will ignore #record_name 171 | %% ]}, 172 | %%173 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 174 | ignored({MacroName, Arity}, {MacroName, Arity}) -> 175 | true; 176 | ignored({MacroName, _Arity}, MacroName) -> 177 | true; 178 | ignored(RecordName, RecordName) -> 179 | true; 180 | ignored(_Pattern, _IgnoreSpec) -> 181 | false. 182 | -------------------------------------------------------------------------------- /src/rules/single_use_hrls.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect header files used in just one module. 2 | %%
To avoid this warning, include the content of the header file into 3 | %% the module.
4 | %% 5 | %%7 | %% This rule assumes that hrl files will not be used outside your project. 8 | %% If you are writing a library that requires your clients to use some of 9 | %% your header files, you can add an ignore rule in rebar.config for it. 10 | %%11 | -module(single_use_hrls). 12 | 13 | -behaviour(hank_rule). 14 | 15 | -export([analyze/2, ignored/2]). 16 | 17 | %% @private 18 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 19 | analyze(FilesAndASTs, _Context) -> 20 | [set_result(HeaderFile, IncludedAtFile) 21 | || {HeaderFile, [IncludedAtFile]} <- build_include_list(FilesAndASTs)]. 22 | 23 | set_result(HeaderFile, IncludedAtFile) -> 24 | #{file => HeaderFile, 25 | line => 0, 26 | text => 27 | hank_utils:format_text("This header file is only included at: ~ts", [IncludedAtFile]), 28 | pattern => undefined}. 29 | 30 | build_include_list(FilesAndASTs) -> 31 | {Files, _ASTs} = lists:unzip(FilesAndASTs), 32 | lists:foldl(fun({File, AST}, Acc) -> 33 | lists:foldl(fun(IncludedFile, AccInner) -> 34 | AtFiles = 35 | case lists:keyfind(IncludedFile, 1, AccInner) of 36 | false -> 37 | []; 38 | {IncludedFile, IncludedAtFiles} -> 39 | IncludedAtFiles 40 | end, 41 | NewTuple = {IncludedFile, [File | AtFiles]}, 42 | lists:keystore(IncludedFile, 1, AccInner, NewTuple) 43 | end, 44 | Acc, 45 | included_files(Files, AST)) 46 | end, 47 | [], 48 | FilesAndASTs). 49 | 50 | included_files(Files, AST) -> 51 | [included_file_path(Files, IncludedFile) 52 | || IncludedFile <- hank_utils:attr_args_concrete(AST, include), 53 | is_file_included(Files, IncludedFile) =/= false]. 54 | 55 | included_file_path(Files, IncludedFile) -> 56 | case is_file_included(Files, IncludedFile) of 57 | false -> 58 | IncludedFile; 59 | IncludedFileWithPath -> 60 | IncludedFileWithPath 61 | end. 62 | 63 | is_file_included(Files, IncludedFile) -> 64 | MatchFunc = fun(File) -> hank_utils:paths_match(IncludedFile, File) end, 65 | case lists:search(MatchFunc, Files) of 66 | {value, IncludedFileWithPath} -> 67 | IncludedFileWithPath; 68 | _ -> 69 | false 70 | end. 71 | 72 | %% @doc It doesn't make sense to provide individual ignore spec support here. 73 | %% The rule's basic unit is already a file. 74 | -spec ignored(hank_rule:ignore_pattern(), term()) -> false. 75 | ignored(undefined, _IgnoreSpec) -> 76 | false. 77 | -------------------------------------------------------------------------------- /src/rules/unnecessary_function_arguments.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect unnecessary function arguments. 2 | %%
The rule emits a warning for each function argument that is consistently 3 | %% ignored in all function clauses.
4 | %%To avoid this warning, remove the unused argument(s).
5 | %%7 | %% This rule will not emit a warning if the function 8 | %% implements a NIF call (assuming that the stub function calls 9 | %%16 | -module(unnecessary_function_arguments). 17 | 18 | %% Throw is used correctly in this module as a nonlocal return within a fold function 19 | -elvis([{elvis_style, no_throw, disable}]). 20 | 21 | -behaviour(hank_rule). 22 | 23 | -export([analyze/2, ignored/2]). 24 | 25 | %% Known OTP behaviours which do not implement dynamic callbacks like ct_suite. 26 | -define(KNOWN_BEHAVIOURS, 27 | [application, 28 | gen_event, 29 | gen_server, 30 | ssh_channel, 31 | ssh_client_channel, 32 | ssh_client_key_api, 33 | ssh_server_channel, 34 | ssh_server_key_api, 35 | ssl_crl_cache_api, 36 | ssl_session_cache_api, 37 | supervisor, 38 | supervisor_bridge, 39 | tftp]). 40 | 41 | %% Allow erl_syntax:syntaxTree/0 type spec 42 | %% Allow Module:behaviour_info/1 call 43 | -elvis([{elvis_style, invalid_dynamic_call, disable}, 44 | {elvis_style, atom_naming_convention, #{regex => "^([a-zA-Z][a-z0-9]*_?)*$"}}]). 45 | 46 | -type imp_callbacks() :: #{File :: string() => [tuple()] | syntax_error}. 47 | 48 | %% @private 49 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 50 | analyze(FilesAndASTs, _Context) -> 51 | ImpCallbacks = callback_usage(FilesAndASTs), 52 | [Result 53 | || {File, AST} <- FilesAndASTs, 54 | not hank_utils:is_old_test_suite(File), 55 | is_parseable(File, ImpCallbacks), 56 | Node <- AST, 57 | erl_syntax:type(Node) == function, 58 | not is_exception_fun(hank_utils:function_tuple(Node)), 59 | not is_callback(Node, File, ImpCallbacks), 60 | Result <- analyze_function(File, Node)]. 61 | 62 | %% @doc Constructs a map with the callbacks of all the files. 63 | %% 1. collect all the behaviors that the file implements. 64 | %% 2. for each one of them, build the list of their possible callbacks. 65 | %% 3. if that list could not be built (usually because of macros), adds 'syntax_error' instead. 66 | -spec callback_usage(hank_rule:asts()) -> imp_callbacks(). 67 | callback_usage(FilesAndASTs) -> 68 | lists:foldl(fun({File, AST}, Result) -> 69 | FoldFun = 70 | fun(Node, FileCallbacks) -> 71 | case hank_utils:node_has_attrs(Node, [behaviour, behavior]) of 72 | true -> 73 | FileCallbacks ++ behaviour_callbacks(Node, AST); 74 | _ -> 75 | FileCallbacks 76 | end 77 | end, 78 | ResultsForFile = 79 | try 80 | erl_syntax_lib:fold(FoldFun, [], erl_syntax:form_list(AST)) 81 | catch 82 | syntax_error -> 83 | syntax_error 84 | end, 85 | maps:put(File, ResultsForFile, Result) 86 | end, 87 | #{}, 88 | FilesAndASTs). 89 | 90 | %% @doc Returns the behaviour's callback list if the given behaviour Node is a "known behaviour", 91 | %% this means it is an OTP behaviour without "dynamic" callbacks. 92 | %% If this is not satisfied or the behaviour attribute contains a macro, 93 | %% this function returns the whole list of functions that exported from the file. 94 | %% That's because, for dynamic behaviors, any exported function can be the implementation 95 | %% of a callback. 96 | -spec behaviour_callbacks(erl_syntax:syntaxTree(), erl_syntax:forms()) -> 97 | [{atom(), non_neg_integer()}]. 98 | behaviour_callbacks(Node, AST) -> 99 | try erl_syntax_lib:analyze_wild_attribute(Node) of 100 | {_, BehaviourMod} -> 101 | case lists:member(BehaviourMod, ?KNOWN_BEHAVIOURS) of 102 | true -> 103 | BehaviourMod:behaviour_info(callbacks); 104 | false -> 105 | module_exports(AST) 106 | end 107 | catch 108 | _:syntax_error -> 109 | %% There is a macro, then return all its exports, just in case 110 | module_exports(AST) 111 | end. 112 | 113 | -spec module_exports(erl_syntax:forms()) -> [{atom(), non_neg_integer()}]. 114 | module_exports(AST) -> 115 | FoldFun = 116 | fun(Node, {ExportAll, Exports, Functions} = Acc) -> 117 | case erl_syntax:type(Node) of 118 | attribute -> 119 | try erl_syntax_lib:analyze_attribute(Node) of 120 | {export, NewExports} -> 121 | {ExportAll, Exports ++ NewExports, Functions}; 122 | {compile, Opts} -> 123 | {ExportAll orelse has_export_all(Opts), Exports, Functions}; 124 | _ -> 125 | Acc 126 | catch 127 | _:syntax_error -> 128 | %% Probably macros, we can't parse this module 129 | throw(syntax_error) 130 | end; 131 | function -> 132 | Function = erl_syntax_lib:analyze_function(Node), 133 | {ExportAll, Exports, [Function | Functions]}; 134 | _ -> 135 | Acc 136 | end 137 | end, 138 | case erl_syntax_lib:fold(FoldFun, {false, [], []}, erl_syntax:form_list(AST)) of 139 | {true, _Exports, Functions} -> 140 | Functions; 141 | {false, Exports, _Functions} -> 142 | Exports 143 | end. 144 | 145 | has_export_all(List) when is_list(List) -> 146 | lists:member(export_all, List); 147 | has_export_all(export_all) -> 148 | true; 149 | has_export_all(_Opt) -> 150 | false. 151 | 152 | %% @doc It will check if arguments are ignored in all function clauses: 153 | %% [(_a, b, _c), (_x, b, c)] 154 | %% [[1, 0, 1], [1, 0, 0]] => [1, 0, 0] => warning 1st param! 155 | %% [(a, _b, c), (_, b, c)] 156 | %% [[0, 1, 0], [1, 0, 0]] => [0, 0, 0] => ok 157 | analyze_function(File, Function) -> 158 | lists:foldl(fun(Result, Acc) -> 159 | case set_result(File, Result) of 160 | ok -> 161 | Acc; 162 | Error -> 163 | [Error | Acc] 164 | end 165 | end, 166 | [], 167 | check_function(Function)). 168 | 169 | set_result(File, {error, Line, Text, IgnorePattern}) -> 170 | #{file => File, 171 | line => Line, 172 | text => Text, 173 | pattern => IgnorePattern}; 174 | set_result(_File, _) -> 175 | ok. 176 | 177 | check_function(FunctionNode) -> 178 | Clauses = erl_syntax:function_clauses(FunctionNode), 179 | ComputedResults = 180 | lists:foldl(fun(Clause, Result) -> 181 | case is_clause_a_nif_stub(Clause) of 182 | true -> 183 | Result; %% Discard NIF stubs! 184 | false -> 185 | Patterns = erl_syntax:clause_patterns(Clause), 186 | ClausePatterns = 187 | [pattern_to_integer(Pattern) || Pattern <- Patterns], 188 | check_unused_args(Result, ClausePatterns) 189 | end 190 | end, 191 | [], 192 | Clauses), 193 | check_computed_results(FunctionNode, ComputedResults). 194 | 195 | %% @doc Checks if the last expression in a clause body applies erlang:nif_error/x 196 | is_clause_a_nif_stub(Clause) -> 197 | LastClauseBodyNode = 198 | lists:last( 199 | erl_syntax:clause_body(Clause)), 200 | case hank_utils:application_node_to_mfa(LastClauseBodyNode) of 201 | {"erlang", "nif_error", _Args} -> 202 | true; 203 | _ -> 204 | false 205 | end. 206 | 207 | %% @doc Checks if the given function node implements a callback 208 | -spec is_callback(erl_syntax:syntaxTree(), string(), imp_callbacks()) -> boolean(). 209 | is_callback(FunctionNode, File, ImpCallbacks) -> 210 | lists:member( 211 | hank_utils:function_tuple(FunctionNode), maps:get(File, ImpCallbacks, [])). 212 | 213 | %% @doc Allows exceptions for functions whose name and arity are known but are 214 | %% not associated with a given behaviour (e.g. parse_transform/2) 215 | is_exception_fun({parse_transform, 2}) -> 216 | true; 217 | is_exception_fun(_) -> 218 | false. 219 | 220 | %% @doc Returns true if hank could parse the file. 221 | %% Otherwise the file is ignored and no warnings are reported for it 222 | -spec is_parseable(string(), imp_callbacks()) -> boolean(). 223 | is_parseable(File, ImpCallbacks) -> 224 | maps:get(File, ImpCallbacks, []) =/= syntax_error. 225 | 226 | %% @doc Computes position by position (multiply/and) 227 | %% Will be 1 only when an argument is unused over all the function clauses 228 | check_unused_args([], Arguments) -> 229 | Arguments; 230 | check_unused_args(Result, Arguments) -> 231 | lists:zipwith(fun(A, B) -> A * B end, Result, Arguments). 232 | 233 | pattern_to_integer({var, _Line, ArgNameAtom}) -> 234 | is_arg_ignored(atom_to_list(ArgNameAtom)); 235 | pattern_to_integer(_) -> 236 | 0. 237 | 238 | is_arg_ignored("_") -> 239 | 1; 240 | is_arg_ignored("_" ++ _) -> 241 | 1; 242 | is_arg_ignored(_) -> 243 | 0. 244 | 245 | check_computed_results(FunctionNode, Results) -> 246 | {_, Errors} = 247 | lists:foldl(fun(Result, {ArgNum, Errors}) -> 248 | NewErrors = 249 | case Result of 250 | 0 -> 251 | Errors; 252 | 1 -> 253 | [set_error(FunctionNode, ArgNum) | Errors] 254 | end, 255 | {ArgNum + 1, NewErrors} 256 | end, 257 | {1, []}, 258 | Results), 259 | Errors. 260 | 261 | set_error(FuncNode, ArgNum) -> 262 | Line = hank_utils:node_line(FuncNode), 263 | FuncDesc = hank_utils:function_description(FuncNode), 264 | Text = hank_utils:format_text("~ts doesn't need its #~p argument", [FuncDesc, ArgNum]), 265 | FuncName = hank_utils:function_name(FuncNode), 266 | IgnorePattern = {list_to_atom(FuncName), erl_syntax:function_arity(FuncNode), ArgNum}, 267 | {error, Line, Text, IgnorePattern}. 268 | 269 | %% @doc Rule ignore specifications. Example: 270 | %%erlang:nif_error/1,2
) or if it's a behaviour callback. 10 | %% In particular, the rule will not emit a warning for any exported 11 | %% function in modules that implement non-OTP behaviors or OTP behaviors 12 | %% that have dynamic callbacks, likegen_statem
orct_suite
. 13 | %% It will also not emit a warning if the function is "known" 14 | %% even if not in a behaviour, like parse_transform/2. 15 | %%
271 | %% -hank([{unnecessary_function_arguments, 272 | %% %% You can give a list of multiple specs or a single one 273 | %% [%% Will ignore any unused argument from ignore_me/2 within the module 274 | %% {ignore_me, 2}, 275 | %% %% Will ignore the 2nd argument from ignore_me_too/3 within the module 276 | %% {ignore_me_too, 3, 2}, 277 | %% %% Will ignore any unused argument from any ignore_me_again/x 278 | %% %% within the module (no matter the function arity) 279 | %% ignore_me_again]}]). 280 | %%281 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 282 | ignored(Pattern, Pattern) -> 283 | true; 284 | ignored({FuncName, _, _}, FuncName) -> 285 | true; 286 | ignored({FuncName, FuncArity, _}, {FuncName, FuncArity}) -> 287 | true; 288 | ignored(_Pattern, _IgnoreSpec) -> 289 | false. 290 | -------------------------------------------------------------------------------- /src/rules/unused_callbacks.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect unused callbacks. 2 | %%
This rule will check all callbacks defined in a module and find 3 | %% those that are not used anywhere in the module itself.
4 | %%It will emit a warning if it can't find the callback's atom name 5 | %% being used anywhere within a module. It will NOT emit a warning if an atom 6 | %% named as a callback is being used, no matter for what that atom is used.
7 | %%This limitation is due to the fact that there are many ways to call 8 | %% a function in Erlang (particularly when dynamic calls are involved).
9 | %%The assumption is that if you define a callback for a behavior, 10 | %% your generic module (where the callback is defined) should call 11 | %% that function at some point, using the implementation provided 12 | %% by the specific module (the one that implements the behavior).
13 | %%To avoid this warning, remove the unused callback definition.
14 | %% 15 | %%17 | %% For this rule to apply, it's assumed that callbacks defined for a 18 | %% particular behavior are only used within the same module that defines it. 19 | %% If you define behaviors in your project and you use their callbacks from 20 | %% other modules, you can add an ignore rule in rebar.config 21 | %% for it. 22 | %%23 | %% @todo [#81 + #82] Correctly handle macros 24 | -module(unused_callbacks). 25 | 26 | -behaviour(hank_rule). 27 | 28 | -export([analyze/2, ignored/2]). 29 | 30 | %% @private 31 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 32 | analyze(FilesAndASTs, _Context) -> 33 | [Result || {File, AST} <- FilesAndASTs, Result <- analyze_file(File, AST)]. 34 | 35 | analyze_file(File, AST) -> 36 | CbNodes = [Node || Node <- AST, hank_utils:node_has_attrs(Node, [callback])], 37 | Callbacks = 38 | lists:map(fun(CbNode) -> 39 | [CbDataArgs | _] = erl_syntax:attribute_arguments(CbNode), 40 | [CbDataTuple | _] = erl_syntax:tuple_elements(CbDataArgs), 41 | [CbName, CBArit] = erl_syntax:tuple_elements(CbDataTuple), 42 | {hank_utils:node_line(CbNode), 43 | erl_syntax:atom_value(CbName), 44 | erl_syntax:integer_value(CBArit)} 45 | end, 46 | CbNodes), 47 | analyze_callbacks(File, AST, Callbacks). 48 | 49 | analyze_callbacks(_File, _AST, []) -> 50 | []; %% Skip files with no callback definitions 51 | analyze_callbacks(File, AST, Callbacks) -> 52 | [set_result(File, Line, Callback, Arity) 53 | || {Line, Callback, Arity} <- Callbacks, not is_used_callback(Callback, AST)]. 54 | 55 | is_used_callback(Callback, Nodes) -> 56 | lists:any(fun(Node) -> hank_utils:node_has_atom(Node, Callback) end, Nodes). 57 | 58 | set_result(File, Line, Callback, Arity) -> 59 | #{file => File, 60 | line => Line, 61 | text => 62 | hank_utils:format_text("Callback ~tw/~B is not used anywhere in the module", 63 | [Callback, Arity]), 64 | pattern => {Callback, Arity}}. 65 | 66 | %% @doc Rule ignore specifications. Example: 67 | %%
68 | %% -hank([{unused_callbacks, 69 | %% [all, %% Will ignore all versions of the all callback (i.e. any arity) 70 | %% {just, 1} %% Will ignore just(term()) but not just() nor just(_, _) callbacks 71 | %% ]}, 72 | %%73 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 74 | ignored({Callback, Arity}, {Callback, Arity}) -> 75 | true; 76 | ignored({Callback, _Arity}, Callback) -> 77 | true; 78 | ignored(_Pattern, _IgnoreSpec) -> 79 | false. 80 | -------------------------------------------------------------------------------- /src/rules/unused_configuration_options.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect unused configuration options 2 | %% It will find options that are no longer used around the code: 3 | %% - All the options from the *.config files 4 | %% (excepting rebar.config, elvis.config and relx.config) 5 | %% - The env list inside any *.app.src files 6 | %%
To avoid this warning, remove the unused parameters.
7 | %% 8 | %%10 | %% For this rule to apply, it's assumed that configuration options for an 11 | %% Erlang application are only consumed within said Erlang application or 12 | %% the other applications in the same umbrella project. 13 | %% If you have a dependency that consumes an environment parameter from one 14 | %% of your project applications, you can add an ignore rule in rebar.config 15 | %% for it. 16 | %%17 | -module(unused_configuration_options). 18 | 19 | -behaviour(hank_rule). 20 | 21 | -export([analyze/2, ignored/2]). 22 | 23 | -define(IGNORED_FILES, ["rebar.config", "elvis.config", "relx.config"]). 24 | 25 | %% @doc Detects unused config options. 26 | %% It gets the options from .config and .app.src files and then: 27 | %%
119 | %% {hank, [{ignore, [ 120 | %% {"this_file.config", unused_configuration_options, [ignore_option]} 121 | %% ]}]}. 122 | %%123 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 124 | ignored(Option, Option) -> 125 | true; 126 | ignored(_, _) -> 127 | false. 128 | -------------------------------------------------------------------------------- /src/rules/unused_hrls.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect unused header files. 2 | %%
To avoid this warning, remove the unused header files.
3 | %% 4 | %%6 | %% This rule assumes that hrl files will not be used outside your project. 7 | %% If you are writing a library that requires your clients to use some of 8 | %% your header files, you can add an ignore rule in rebar.config for it. 9 | %%10 | %% @todo Figure out the absname of IncludePath 11 | %% [https://github.com/AdRoll/rebar3_hank/issues/31] 12 | -module(unused_hrls). 13 | 14 | -behaviour(hank_rule). 15 | 16 | -export([analyze/2, ignored/2]). 17 | 18 | %% @private 19 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 20 | analyze(FilesAndASTs, Context) -> 21 | {Files, ASTs} = lists:unzip(FilesAndASTs), 22 | IncludePaths = [IncludePath || AST <- ASTs, IncludePath <- include_paths(AST)], 23 | IncludeLibPaths = 24 | [expand_lib_dir(IncludeLibPath, Context) 25 | || AST <- ASTs, IncludeLibPath <- include_lib_paths(AST)], 26 | [#{file => File, 27 | line => 0, 28 | text => "This file is unused", 29 | pattern => undefined} 30 | || File <- Files, 31 | filename:extension(File) == ".hrl", 32 | is_unused_local(File, IncludePaths), 33 | is_unused_lib(File, IncludeLibPaths)]. 34 | 35 | include_paths(AST) -> 36 | [erl_syntax:concrete(IncludedFile) 37 | || Node <- AST, 38 | % Yeah, include_lib can also be used as include ¯\_(ツ)_/¯ (check epp's code) 39 | hank_utils:node_has_attrs(Node, [include, include_lib]), 40 | IncludedFile <- erl_syntax:attribute_arguments(Node)]. 41 | 42 | include_lib_paths(AST) -> 43 | hank_utils:attr_args_concrete(AST, include_lib). 44 | 45 | is_unused_local(FilePath, IncludePaths) -> 46 | not 47 | lists:any(fun(IncludePath) -> hank_utils:paths_match(IncludePath, FilePath) end, 48 | IncludePaths). 49 | 50 | is_unused_lib(File, IncludeLibPaths) -> 51 | % Note that IncludeLibPaths here are absolute paths, not relative ones. 52 | not 53 | lists:member( 54 | filename:absname(File), IncludeLibPaths). 55 | 56 | expand_lib_dir(IncludeLibPath, Context) -> 57 | [App | Path] = filename:split(IncludeLibPath), 58 | case hank_context:app_dir(list_to_atom(App), Context) of 59 | undefined -> 60 | IncludeLibPath; 61 | AppDir -> 62 | fname_join([AppDir | Path]) 63 | end. 64 | 65 | %% @doc Copied verbatim from epp:fname_join(Name). 66 | fname_join(["." | [_ | _] = Rest]) -> 67 | fname_join(Rest); 68 | fname_join(Components) -> 69 | filename:join(Components). 70 | 71 | %% @doc It doesn't make sense to provide individual ignore spec support here. 72 | %% The rule's basic unit is already a file. 73 | -spec ignored(hank_rule:ignore_pattern(), term()) -> false. 74 | ignored(undefined, _IgnoreSpec) -> 75 | false. 76 | -------------------------------------------------------------------------------- /src/rules/unused_macros.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect unused macros. 2 | %%
To avoid this warning, remove the unused macros.
3 | %% Note that for header files, this rule will fail to detect some unused 4 | %% macros. Particularly, in the case where you have an unused macro defined 5 | %% in a header file and another macro with the same name and arity defined 6 | %% somewhere else that is used. 7 | %% Since determining precisely what files are included in each -include 8 | %% attribute is not trivial, Hank will act conservatively and not make any 9 | %% effort to verify where each macro that's used is defined. 10 | %% So, if you have a project with multiple definitions of the same macro 11 | %% with the same arity... well... as long as one of them is used, none of 12 | %% them will be reported as unused. 13 | %% 14 | %%16 | %% This rule assumes that hrl files will not be used outside your project. 17 | %% If you are writing a library that requires your clients to use a macro 18 | %% defined in some of your header files, you can add an ignore rule in 19 | %% rebar.config for it. 20 | %%21 | %% @todo Detect unparsable macros [https://github.com/AdRoll/rebar3_hank/issues/37] 22 | -module(unused_macros). 23 | 24 | -behaviour(hank_rule). 25 | 26 | -export([analyze/2, ignored/2]). 27 | 28 | %% @private 29 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 30 | analyze(FilesAndASTs, _Context) -> 31 | MacrosInFiles = lists:map(fun macro_usage/1, FilesAndASTs), 32 | AllUsedMacros = [UsedMacro || #{used := Used} <- MacrosInFiles, UsedMacro <- Used], 33 | [Result 34 | || #{file := File, 35 | defined := DefinedMacros, 36 | used := UsedMacros} 37 | <- MacrosInFiles, 38 | Result <- analyze(File, DefinedMacros, UsedMacros, AllUsedMacros)]. 39 | 40 | macro_usage({File, AST}) -> 41 | FoldFun = 42 | fun(Node, {Definitions, Usage}) -> 43 | case erl_syntax:type(Node) of 44 | attribute -> 45 | case hank_utils:attr_name(Node) of 46 | define -> 47 | {[Node | Definitions], Usage}; 48 | ControlFlowAttr 49 | when ControlFlowAttr == ifdef; 50 | ControlFlowAttr == ifndef; 51 | ControlFlowAttr == undef -> 52 | {Definitions, [hank_utils:macro_from_control_flow_attr(Node) | Usage]}; 53 | _ -> 54 | {Definitions, Usage} 55 | end; 56 | macro -> 57 | {Definitions, [Node | Usage]}; 58 | _ -> 59 | {Definitions, Usage} 60 | end 61 | end, 62 | {MacroDefinitions, MacroUsage} = 63 | erl_syntax_lib:fold(FoldFun, {[], []}, erl_syntax:form_list(AST)), 64 | DefinedMacros = lists:map(fun macro_definition_name_and_line/1, MacroDefinitions), 65 | UsedMacros = lists:map(fun macro_application_name/1, MacroUsage), 66 | #{file => File, 67 | defined => DefinedMacros, 68 | used => UsedMacros}. 69 | 70 | analyze(File, DefinedMacros, UsedMacros, AllUsedMacros) -> 71 | case filename:extension(File) of 72 | ".erl" -> 73 | analyze(File, DefinedMacros, UsedMacros); 74 | ".hrl" -> 75 | analyze(File, DefinedMacros, AllUsedMacros); 76 | _ -> 77 | [] 78 | end. 79 | 80 | analyze(File, DefinedMacros, UsedMacros) -> 81 | [result(File, MacroName, MacroArity, MacroLine) 82 | || {MacroName, MacroArity, MacroLine} <- DefinedMacros, 83 | not is_member({MacroName, MacroArity}, UsedMacros)]. 84 | 85 | macro_definition_name_and_line(Node) -> 86 | {MacroName, MacroArity} = hank_utils:macro_definition_name(Node), 87 | Line = hank_utils:node_line(Node), 88 | {MacroName, MacroArity, Line}. 89 | 90 | macro_application_name(Node) -> 91 | {hank_utils:macro_name(Node), hank_utils:macro_arity(Node)}. 92 | 93 | result(File, Name, Arity, Line) -> 94 | Text = 95 | case Arity of 96 | none -> 97 | hank_utils:format_text("?~ts is unused", [Name]); 98 | Arity -> 99 | hank_utils:format_text("?~ts/~p is unused", [Name, Arity]) 100 | end, 101 | #{file => File, 102 | line => Line, 103 | text => Text, 104 | pattern => {Name, Arity}}. 105 | 106 | %% @doc Rule ignore specifications. Example: 107 | %%
108 | %% -hank([{unused_macros, 109 | %% ["ALL", %% Will ignore ?ALL, ?ALL() and ?ALL(X) 110 | %% {"ZERO", 0}, %% Will ignore ?ZERO() but not ?ZERO(X) nor ?ZERO 111 | %% {"ONE", 1}, %% Will ignore ?ONE(X) but not ?ONE() nor ?ONE 112 | %% {"NONE", none} %% Will ignore ?NONE but not ?NONE(X) nor ?NONE() 113 | %% ]}, 114 | %%115 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 116 | ignored({Name, Arity}, {Name, Arity}) -> 117 | true; 118 | ignored({Name, _Arity}, Name) -> 119 | true; 120 | ignored(_Pattern, _IgnoreSpec) -> 121 | false. 122 | 123 | is_member({MacroName, none}, UsedMacros) -> 124 | lists:keymember(MacroName, 1, UsedMacros); 125 | is_member(Macro, UsedMacros) -> 126 | lists:member(Macro, UsedMacros). 127 | -------------------------------------------------------------------------------- /src/rules/unused_record_fields.erl: -------------------------------------------------------------------------------- 1 | %% @doc A rule to detect unused record fields. 2 | %%
The rule will detect fields that are defined as part of a record but 3 | %% never actually used anywhere.
4 | %%To avoid this warning, remove the unused record fields.
5 | %% Note that for header files, this rule will fail to detect some unused 6 | %% fields. Particularly, in the case where you have an unused field defined 7 | %% in a header file and another record with the same name and the same field 8 | %% defined somewhere else that is used. 9 | %% Since determining precisely what files are included in each -include 10 | %% attribute is not trivial, Hank will act conservatively and not make 11 | %% any effort to verify where each record field that's used is defined. 12 | %% So, if you have a project with multiple definitions of the same record 13 | %% with the same field... well... as long as one of them is used, none of 14 | %% them will be reported as unused. 15 | %% 16 | %%18 | %% This rule assumes that your code will never use the underlying tuple 19 | %% structure of your records directly. 20 | %% If you do so, you can add an ignore rule in rebar.config for it. 21 | %%22 | %% @todo Don't count record construction as usage [https://github.com/AdRoll/rebar3_hank/issues/35] 23 | -module(unused_record_fields). 24 | 25 | -behaviour(hank_rule). 26 | 27 | -export([analyze/2, ignored/2]). 28 | 29 | %% @private 30 | -spec analyze(hank_rule:asts(), hank_context:t()) -> [hank_rule:result()]. 31 | analyze(FilesAndASTs, _Context) -> 32 | Parsed = lists:map(fun field_usage/1, FilesAndASTs), 33 | AllUsedRecords = [UsedRecord || #{used_records := Used} <- Parsed, UsedRecord <- Used], 34 | AllUsedFields = [UsedField || #{used_fields := Used} <- Parsed, UsedField <- Used], 35 | [Result 36 | || #{file := File, 37 | record_definitions := RecordDefinitions, 38 | defined_fields := DefinedFields, 39 | used_records := UsedRecords, 40 | used_fields := UsedFields} 41 | <- Parsed, 42 | Result 43 | <- analyze(File, 44 | RecordDefinitions, 45 | DefinedFields, 46 | UsedRecords, 47 | UsedFields, 48 | AllUsedRecords, 49 | AllUsedFields)]. 50 | 51 | field_usage({File, AST}) -> 52 | FoldFun = 53 | fun(Node, {Records, Usage}) -> 54 | case erl_syntax:type(Node) of 55 | attribute -> 56 | case hank_utils:attr_name(Node) of 57 | record -> 58 | {[Node | Records], Usage}; 59 | _ -> 60 | {Records, Usage} 61 | end; 62 | record_expr -> 63 | {Records, [Node | Usage]}; 64 | record_access -> 65 | {Records, [Node | Usage]}; 66 | record_index_expr -> 67 | {Records, [Node | Usage]}; 68 | _ -> 69 | % Ignored: record_field, typed_record_field, record_type, record_type_field 70 | {Records, Usage} 71 | end 72 | end, 73 | {RecordDefinitions, RecordUsage} = 74 | erl_syntax_lib:fold(FoldFun, {[], []}, erl_syntax:form_list(AST)), 75 | DefinedFields = 76 | [{RecordName, FieldName} 77 | || Node <- RecordDefinitions, {RecordName, FieldName} <- analyze_record_attribute(Node)], 78 | {UsedRecords, UsedFields} = 79 | lists:foldl(fun(Node, {URs, UFs}) -> 80 | case analyze_record_expr(Node) of 81 | {RecordName, all_fields} -> 82 | {[RecordName | URs], UFs}; 83 | Fields -> 84 | {URs, Fields ++ UFs} 85 | end 86 | end, 87 | {[], []}, 88 | RecordUsage), 89 | #{file => File, 90 | record_definitions => RecordDefinitions, 91 | defined_fields => DefinedFields, 92 | used_records => UsedRecords, 93 | used_fields => UsedFields}. 94 | 95 | analyze(File, 96 | RecordDefinitions, 97 | DefinedFields, 98 | UsedRecords, 99 | UsedFields, 100 | AllUsedRecords, 101 | AllUsedFields) -> 102 | case filename:extension(File) of 103 | ".erl" -> 104 | analyze(File, RecordDefinitions, DefinedFields, UsedRecords, UsedFields); 105 | ".hrl" -> 106 | analyze(File, RecordDefinitions, DefinedFields, AllUsedRecords, AllUsedFields); 107 | _ -> 108 | [] 109 | end. 110 | 111 | analyze(File, RecordDefinitions, DefinedFields, UsedRecords, UsedFields) -> 112 | [result(File, RecordName, FieldName, RecordDefinitions) 113 | || {RecordName, FieldName} <- DefinedFields -- UsedFields, 114 | not lists:member(RecordName, UsedRecords)]. 115 | 116 | analyze_record_attribute(Node) -> 117 | try erl_syntax_lib:analyze_record_attribute(Node) of 118 | {RecordName, Fields} -> 119 | [{RecordName, FieldName} || {FieldName, _} <- Fields] 120 | catch 121 | _:syntax_error -> 122 | %% There is a macro in the record definition 123 | [] 124 | end. 125 | 126 | analyze_record_expr(Node) -> 127 | try erl_syntax_lib:analyze_record_expr(Node) of 128 | {record_expr, {RecordName, Fields}} -> 129 | [{RecordName, FieldName} || {FieldName, _} <- Fields]; 130 | {_, {RecordName, FieldName}} -> 131 | [{RecordName, FieldName}] 132 | catch 133 | _:syntax_error -> 134 | %% Probably the record expression uses stuff like #rec{_ = '_'} or Macros 135 | RecordName = 136 | case erl_syntax:type(Node) of 137 | record_expr -> 138 | erl_syntax:record_expr_type(Node); 139 | record_index_expr -> 140 | erl_syntax:record_index_expr_type(Node); 141 | record_access -> 142 | erl_syntax:record_access_type(Node) 143 | end, 144 | {erl_syntax:atom_value(RecordName), all_fields} 145 | end. 146 | 147 | result(File, RecordName, FieldName, RecordDefinitions) -> 148 | L = case find_record_definition(RecordName, RecordDefinitions) of 149 | false -> 150 | 0; 151 | {value, RecordDefinition} -> 152 | [_, RecordFields] = erl_syntax:attribute_arguments(RecordDefinition), 153 | case find_record_field(FieldName, erl_syntax:tuple_elements(RecordFields)) of 154 | false -> 155 | hank_utils:node_line(RecordDefinition); 156 | {value, FieldDefinition} -> 157 | hank_utils:node_line(FieldDefinition) 158 | end 159 | end, 160 | #{file => File, 161 | line => L, 162 | text => 163 | hank_utils:format_text("Field ~tp in record ~tp is unused", [FieldName, RecordName]), 164 | pattern => {RecordName, FieldName}}. 165 | 166 | find_record_definition(RecordName, Definitions) -> 167 | lists:search(fun(Definition) -> 168 | case erl_syntax:attribute_arguments(Definition) of 169 | [RN | _] -> 170 | erl_syntax:type(RN) == atom 171 | andalso erl_syntax:atom_value(RN) == RecordName; 172 | [] -> 173 | false 174 | end 175 | end, 176 | Definitions). 177 | 178 | find_record_field(FieldName, Definitions) -> 179 | lists:search(fun(Definition) -> 180 | {FN, _} = erl_syntax_lib:analyze_record_field(Definition), 181 | FN == FieldName 182 | end, 183 | Definitions). 184 | 185 | %% @doc Rule ignore specifications. Example: 186 | %%
187 | %% -hank([{unused_record_fields, 188 | %% [a_record, %% Will ignore all fields in #a_record 189 | %% {a_record, a_field} %% Will ignore #a_record.a_field 190 | %% ]}]). 191 | %%192 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 193 | ignored({RecordName, FieldName}, {RecordName, FieldName}) -> 194 | true; 195 | ignored({RecordName, _FieldName}, RecordName) -> 196 | true; 197 | ignored(_Pattern, _IgnoreSpec) -> 198 | false. 199 | -------------------------------------------------------------------------------- /test.dict: -------------------------------------------------------------------------------- 1 | _ 2 | _arg2 3 | a_rec 4 | a_record 5 | app0 6 | app1 7 | app1_include_lib 8 | app2 9 | app2_include_lib 10 | app_header_3 11 | arg2 12 | attr_αåβö 13 | global_rejector 14 | macro_0 15 | macro_1 16 | macro_all 17 | macro_from_config 18 | macro_none 19 | really_unused_field 20 | really_unused_record 21 | some_macro_3 22 | test_app 23 | test_app_suite 24 | unicode_αåβö 25 | unused_field 26 | unused_field_with_default 27 | unused_hrls 28 | unused_ignores 29 | unused_typed_field 30 | unused_typed_field_with_default 31 | used_macro_sample 32 | -------------------------------------------------------------------------------- /test/files/hidden/.hidden.module.erl: -------------------------------------------------------------------------------- 1 | -module('.hidden.module'). 2 | 3 | -define(UNUSED_MACRO, value). 4 | -------------------------------------------------------------------------------- /test/files/hidden/.hidden_folder/regular_module.erl: -------------------------------------------------------------------------------- 1 | -module(regular_module). 2 | 3 | -define(UNUSED_MACRO, value). 4 | -------------------------------------------------------------------------------- /test/files/hidden/_hidden_folder/regular_module.erl: -------------------------------------------------------------------------------- 1 | -module(regular_module). 2 | 3 | -define(UNUSED_MACRO, value). 4 | -------------------------------------------------------------------------------- /test/files/hidden/_hidden_module.erl: -------------------------------------------------------------------------------- 1 | -module('_hidden_module'). 2 | 3 | -define(UNUSED_MACRO, value). 4 | -------------------------------------------------------------------------------- /test/files/ignore/no_ignore.erl: -------------------------------------------------------------------------------- 1 | -module(no_ignore). 2 | 3 | %% This breaks erl_syntax_lib:analyze_attribute 4 | -ignore_xref([{?MODULE, no_ignore, 0}]). 5 | 6 | -export([no_ignore/0]). 7 | 8 | no_ignore(_, _) -> 9 | no_ignore. 10 | -------------------------------------------------------------------------------- /test/files/ignore/no_ignore.hrl: -------------------------------------------------------------------------------- 1 | -define(NO_IGNORE, no_ignore). 2 | -------------------------------------------------------------------------------- /test/files/ignore/rebar_config_ignore.erl: -------------------------------------------------------------------------------- 1 | -module(rebar_config_ignore). 2 | 3 | -define(unused_macro, unused_macro). 4 | 5 | unnecessary_function_arguments(_) -> unnecessary_function_arguments. 6 | -------------------------------------------------------------------------------- /test/files/ignore/specific_ignore.erl: -------------------------------------------------------------------------------- 1 | -module(specific_ignore). 2 | 3 | -hank([unused_macros, 4 | {unnecessary_function_arguments, [{no_ignore, 2}, {do_ignore, 1, 1}, do_ignore_me_too]}]). 5 | 6 | -define(UNUSED_MACRO, unused_macro). 7 | 8 | -export([no_ignore/2, do_ignore/1, do_ignore_me_too/1, do_ignore_me_too/2, 9 | do_ignore_me_too/3]). 10 | 11 | no_ignore(_, _) -> 12 | no_ignore. 13 | 14 | do_ignore(_Arg1) -> 15 | ok. 16 | 17 | do_ignore_me_too(_Arg1) -> 18 | ok. 19 | 20 | do_ignore_me_too(_Arg1, _Arg2) -> 21 | ok. 22 | 23 | do_ignore_me_too(_Arg1, _Arg2, _Arg3) -> 24 | ok. 25 | -------------------------------------------------------------------------------- /test/files/ignore/with_ignore.erl: -------------------------------------------------------------------------------- 1 | -module(with_ignore). 2 | 3 | -hank ignore. 4 | 5 | -export([with_ignore/0]). 6 | 7 | with_ignore(_, _) -> 8 | with_ignore. 9 | -------------------------------------------------------------------------------- /test/files/ignore/with_ignore.hrl: -------------------------------------------------------------------------------- 1 | -define(WITH_IGNORE, with_ignore). 2 | 3 | -hank ignore. 4 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/include/flow.hrl: -------------------------------------------------------------------------------- 1 | -define(IFNDEF, true). 2 | -define(IFDEF, true). 3 | -define(IF(X), not X). 4 | -define(UNDEF, undef). 5 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/include/header1.hrl: -------------------------------------------------------------------------------- 1 | -define(APP_HEADER_1, "this is header from app that will be used in just one module"). 2 | -define(SOME_MACRO_1(A), A). 3 | -define(THIS_MACRO, {is_used, "and", it, shouldnt, generate, a, warning}). 4 | -define(SOME_DEFINE, ?THIS_MACRO). 5 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/include/header2.hrl: -------------------------------------------------------------------------------- 1 | -define(APP_HEADER_2, "this is header from app that will be used in just one module"). 2 | -define(SOME_MACRO_2(A), A). 3 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/include/header3.hrl: -------------------------------------------------------------------------------- 1 | -define(APP_HEADER_3, "this is header from app that will be used in different modules"). 2 | -define(SOME_MACRO_3(A), A). 3 | 4 | -record(a_record, {used_field, used_typed_field :: used_typed_field}). 5 | -record(another_record, {used_field, used_typed_field :: used_typed_field}). 6 | %% Unicode should be supported and not break! 7 | -record('unicode_αåβö', {'attr_αåβö' :: a_type()}). 8 | 9 | %% This doesn't count as usage 10 | -type a_type() :: #a_record{}. 11 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/include/ignore.hrl: -------------------------------------------------------------------------------- 1 | -hank([{single_use_hrl_attrs, 2 | ["MACRO_ALL", 3 | {"MACRO_0", 0}, 4 | {"MACRO_1", 1}, 5 | {"MACRO_NONE", none}, 6 | ignored_record, 7 | reported_record]}]). 8 | 9 | -define(MACRO_ALL, "this macro is always ignored"). 10 | -define(MACRO_ALL(), "regardless of its arity"). 11 | -define(MACRO_ALL(It), "is never reported as unused"). 12 | -define(MACRO_ALL(Not, Even), "if it has multiple arguments"). 13 | -define(MACRO_0, "This version of the macro should not be ignored"). 14 | -define(MACRO_0(), "This one should since it has 0 arguments"). 15 | -define(MACRO_0(And), "this one should be reported since it has one"). 16 | -define(MACRO_0(Also, This), "one, that has 2"). 17 | -define(MACRO_1, "For this macro"). 18 | -define(MACRO_1(), "there should be a report"). 19 | -define(MACRO_1(But), "not for this version with arity 1"). 20 | -define(MACRO_1(Just, For), "all the others"). 21 | -define(MACRO_NONE, "This instance should be ignored"). 22 | -define(MACRO_NONE(), "But there should be a report"). 23 | -define(MACRO_NONE(For), "each of the"). 24 | -define(MACRO_NONE(Other, Versions), ""). 25 | 26 | -record(ignored_record, {list}). 27 | -record(reported_record, {list}). 28 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/src/app.app.src: -------------------------------------------------------------------------------- 1 | {application, app, [{vsn, "0"}]}. 2 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/src/app_include.erl: -------------------------------------------------------------------------------- 1 | -module(app_include). 2 | 3 | -include("header2.hrl"). 4 | -include("header3.hrl"). 5 | 6 | -export([my_function/0]). 7 | 8 | my_function() -> 9 | % those are only used here! 10 | Val = ?SOME_MACRO_2(?APP_HEADER_2), 11 | % and those are used in other module :) 12 | Val ++ ?SOME_MACRO_3(?APP_HEADER_3). 13 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/src/app_include_lib.erl: -------------------------------------------------------------------------------- 1 | -module(app_include_lib). 2 | 3 | -include_lib("app/include/header1.hrl"). 4 | 5 | -export([my_function/0]). 6 | 7 | my_function() -> 8 | % those are only used here! 9 | ?SOME_MACRO_1(?APP_HEADER_1). 10 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/src/app_other.erl: -------------------------------------------------------------------------------- 1 | -module(app_other). 2 | 3 | -include("ignore.hrl"). 4 | -include("header1.hrl"). 5 | -include("header3.hrl"). 6 | 7 | -record(yet_another_record, {another_record :: #another_record{}}). 8 | 9 | -export([my_function/0]). 10 | 11 | my_function() -> 12 | R = #a_record{used_field = used_field, used_typed_field = used_typed_field}, 13 | U = #'unicode_αåβö'{'attr_αåβö' = R}, 14 | _ = ?SOME_MACRO_3(U), 15 | ?APP_HEADER_3 ++ ?SOME_DEFINE. 16 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/src/flow.erl: -------------------------------------------------------------------------------- 1 | -module(flow). 2 | 3 | -include("flow.hrl"). 4 | 5 | -export([ifndef/0, iif/0]). 6 | 7 | -ifndef(IFNDEF). 8 | ifndef() -> notdefined. 9 | -else. 10 | ifndef() -> defined. 11 | -endif. 12 | 13 | 14 | -ifdef(IFDEF). 15 | -type ifdef() :: ifdef. 16 | -export_type([ifdef/0]). 17 | -endif. 18 | 19 | -if(?IF(?VALUE)). 20 | 21 | iif() -> true. 22 | 23 | -elif(not ?IF(?VALUE)). 24 | 25 | iif() -> false. 26 | 27 | -else. 28 | 29 | iif() -> wat. 30 | 31 | -endif. 32 | 33 | -undef(UNDEF). 34 | -------------------------------------------------------------------------------- /test/files/single_use_hrl_attrs/lib/app/src/ignore.erl: -------------------------------------------------------------------------------- 1 | -module ignore. 2 | 3 | -include("ignore.hrl"). 4 | 5 | -export([usage/0]). 6 | 7 | usage() -> 8 | #{ignored => 9 | #ignored_record{list = 10 | [?MACRO_ALL, 11 | ?MACRO_ALL(), 12 | ?MACRO_ALL(x), 13 | ?MACRO_ALL(x, x), 14 | ?MACRO_0(), 15 | ?MACRO_1(x), 16 | ?MACRO_NONE]}, 17 | reported => 18 | #reported_record{list = 19 | [?MACRO_0, 20 | ?MACRO_0(x), 21 | ?MACRO_0(x, x), 22 | ?MACRO_1, 23 | ?MACRO_1(), 24 | ?MACRO_1(x, x), 25 | ?MACRO_NONE(), 26 | ?MACRO_NONE(x), 27 | ?MACRO_NONE(x, x)]}}. 28 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/include/ignored.hrl: -------------------------------------------------------------------------------- 1 | -hank ignore. 2 | 3 | -define(HEADER_IGNORED, "this header file is included at include_ignored.erl"). 4 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/include/multi.hrl: -------------------------------------------------------------------------------- 1 | -define(HEADER_MULTI, 2 | "this header file is included at include_multi.erl and include_single.erl"). 3 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/include/single.hrl: -------------------------------------------------------------------------------- 1 | -define(HEADER_SINGLE, "this header file is included at include_single.erl"). 2 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/include/single_unicode.hrl: -------------------------------------------------------------------------------- 1 | -define(HEADER_SINGLE_UNICODE, "this header file is included at include_unicode_åö.erl"). 2 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/src/include_ignored.erl: -------------------------------------------------------------------------------- 1 | -module(include_ignored). 2 | 3 | -include("ignored.hrl"). 4 | -include("multi.hrl"). 5 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/src/include_missing.erl: -------------------------------------------------------------------------------- 1 | -module(include_missing). 2 | 3 | -include("missing.hrl"). 4 | -include("multi.hrl"). 5 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/src/include_multi.erl: -------------------------------------------------------------------------------- 1 | -module(include_multi). 2 | 3 | -include("multi.hrl"). 4 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/src/include_single.erl: -------------------------------------------------------------------------------- 1 | -module(include_single). 2 | 3 | -include("single.hrl"). 4 | -include("multi.hrl"). 5 | -------------------------------------------------------------------------------- /test/files/single_use_hrls/src/include_unicode_åö.erl: -------------------------------------------------------------------------------- 1 | -module(include_unicode_åö). 2 | 3 | -include("single_unicode.hrl"). 4 | -include("multi.hrl"). 5 | -------------------------------------------------------------------------------- /test/files/test_app/another_sys.config: -------------------------------------------------------------------------------- 1 | [ 2 | {test_app, [ 3 | {this_used_key, test}, 4 | {another_used_key, test}, 5 | {yet_another_key, ok}, 6 | {yet_another_key_2, ok}, 7 | {yet_another_key_3, [{part, [0,0,1]}]}, 8 | {my_great_option, 0}, 9 | {another_great_option, 0} 10 | ]} 11 | ]. 12 | -------------------------------------------------------------------------------- /test/files/test_app/elvis.config: -------------------------------------------------------------------------------- 1 | %% This is not really used, but the rule `unused_configuration_options` should ignore this whole file :) 2 | [ 3 | { 4 | elvis, 5 | [ 6 | {config, 7 | [#{dirs => ["include"], 8 | filter => "*.hrl", 9 | rules => [ 10 | {elvis_text_style, line_length, #{limit => 150, skip_comments => false}}, 11 | {elvis_text_style, no_tabs}, 12 | {elvis_text_style, no_trailing_whitespace}, 13 | {elvis_style, macro_module_names}, 14 | {elvis_style, no_if_expression}, 15 | {elvis_style, no_behavior_info} 16 | ] 17 | }, 18 | #{dirs => ["."], 19 | filter => "elvis.config", 20 | ruleset => elvis_config 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | ]. 27 | -------------------------------------------------------------------------------- /test/files/test_app/include/a_folder/a_header.hrl: -------------------------------------------------------------------------------- 1 | -define(A_SUB_MACRO, a_sub_macro). 2 | -------------------------------------------------------------------------------- /test/files/test_app/include/a_header.hrl: -------------------------------------------------------------------------------- 1 | -define(A_MACRO, a_macro). 2 | -------------------------------------------------------------------------------- /test/files/test_app/lib/a_project/src/a_folder/a_subfolder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/one_tuple.config: -------------------------------------------------------------------------------- 1 | %% Taken from erlang/otp 2 | {unix,[{telnet,"belegost"},{username,"telnet-test"},{password,"tset-tenlet"},{keep_alive,true}]}. 3 | -------------------------------------------------------------------------------- /test/files/test_app/rebar.config: -------------------------------------------------------------------------------- 1 | %% This is not really used, but the rule `unused_configuration_options` should ignore this whole file :) 2 | {erl_opts, [ 3 | debug_info, 4 | warnings_as_errors 5 | ]}. 6 | 7 | {minimum_otp_vsn, "23"}. 8 | 9 | {deps, []}. 10 | 11 | {profiles, []}. 12 | 13 | {project_plugins, [ 14 | {rebar3_format, "~> 0.9.0"} 15 | ]}. 16 | 17 | {eunit_opts, [verbose]}. 18 | 19 | {ct_opts, [{sys_config, "rebar3.test.config"}]}. 20 | -------------------------------------------------------------------------------- /test/files/test_app/relx.config: -------------------------------------------------------------------------------- 1 | %% This is not really used, but the rule `unused_configuration_options` should ignore this whole file :) 2 | {release, 3 | {erlorg, "1.0.0"}, 4 | [erlorg]}. 5 | 6 | {sys_config, "./rel/sys.config"}. 7 | 8 | { overlay 9 | , [ {copy, "assets", "assets"} 10 | ] 11 | }. 12 | 13 | {extended_start_script, true}. -------------------------------------------------------------------------------- /test/files/test_app/src/a_folder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/src/a_folder/a_subfolder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/src/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/src/test_app.app.src: -------------------------------------------------------------------------------- 1 | {application, test_app, [ 2 | {description, "An OTP application"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, {'test_app_a_module', []}}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {env, [ 11 | {my_config_from_app_src, an_atom}, 12 | {other_config_from_app_src, <<"other">>} 13 | ]}, 14 | {modules, []} 15 | ]}. 16 | -------------------------------------------------------------------------------- /test/files/test_app/sys.config: -------------------------------------------------------------------------------- 1 | [ 2 | {test_app, [ 3 | {environment, test}, 4 | {a_key, ok}, 5 | {not_used_but_ignored, hum} 6 | ]}, 7 | "another_sys.config" 8 | ]. 9 | -------------------------------------------------------------------------------- /test/files/test_app/test/a_folder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/test/a_module_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/unconventional.config: -------------------------------------------------------------------------------- 1 | #{this => file, 2 | holds => an, 3 | unconventional => config, 4 | since => it, 5 | has => "a map", 6 | <<"instead_of">> => [a, list, 'of', tuples]}. 7 | -------------------------------------------------------------------------------- /test/files/test_app/user.config: -------------------------------------------------------------------------------- 1 | this-is-a-user-config-file: "which is yaml" 2 | other-props: 3 | - one 4 | - two 5 | - three 6 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/include/a_folder/a_header.hrl: -------------------------------------------------------------------------------- 1 | -define(A_SUB_MACRO, a_sub_macro). 2 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/include/a_header.hrl: -------------------------------------------------------------------------------- 1 | -define(A_MACRO, a_macro). 2 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/lib/a_project/src/a_folder/a_subfolder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | 3 | -include("a_header.hrl"). 4 | -include("a_folder/a_header.hrl"). 5 | 6 | -define(A_MACRO_WITH_AN_OPTION, application:get_env(my_config_from_app_src)). 7 | 8 | -export([a_function/0]). 9 | 10 | a_function() -> 11 | [?A_MACRO_WITH_AN_OPTION, 12 | ?A_MACRO, 13 | ?A_SUB_MACRO, 14 | application:get_env(test_app, environment, default_value)]. 15 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/lib/a_project/src/a_folder/a_subfolder/another_module.erl: -------------------------------------------------------------------------------- 1 | -module(another_module). 2 | 3 | -include("a_header.hrl"). 4 | -include("a_folder/a_header.hrl"). 5 | 6 | -define(A_COMPLEX_MACRO, 7 | fun() -> 8 | All = application:get_all_env(test_app), 9 | proplists:get_value(this_used_key, All) 10 | end). 11 | -define(A_COMPLEX_MACRO(App), 12 | fun() -> 13 | All = application:get_all_env(App), 14 | proplists:get_value(another_used_key, All) 15 | end). 16 | -define(A_COMPLEX_MACRO(), 17 | fun() -> 18 | Value = application:get_env(yet_another_key), 19 | list_to_binary(Value) 20 | end). 21 | -define(A_MACRO_FUN(), fun() -> application:get_env(yet_another_key_2) end). 22 | -define(A_MACRO_FUN_USED, ?A_MACRO_FUN()). 23 | -define(A_MACRO_WITH_BODY, 24 | begin 25 | erlang:time(), 26 | application:get_env(yet_another_key_3) 27 | end). 28 | 29 | -record(state, {foo = application:get_env(my_great_option), bar = ok}). 30 | 31 | -export([a_function/0]). 32 | 33 | a_function() -> 34 | R = #state{}, 35 | R#state{bar = application:get_env(another_great_option)}, 36 | [proplists:get_value(a_key, application:get_all_env(my_app)), 37 | ?A_MACRO, 38 | ?A_SUB_MACRO, 39 | ?A_COMPLEX_MACRO, 40 | ?A_COMPLEX_MACRO(test_app), 41 | ?A_COMPLEX_MACRO(), 42 | ?A_MACRO_FUN_USED, 43 | R#state.foo, 44 | R#state.bar, 45 | ?A_MACRO_WITH_BODY, 46 | application:get_env(other_config_from_app_src)]. 47 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/src/a_folder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/src/a_folder/a_subfolder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/src/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/test/a_folder/a_module.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/test_app/without_warnings/test/a_module_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(a_module). 2 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/a_behaviour.erl: -------------------------------------------------------------------------------- 1 | -module(a_behaviour). 2 | 3 | -callback the_magic() -> any(). 4 | -callback a_kind_of_magic(any()) -> any(). 5 | 6 | -export([the_magic/1, a_kind_of_magic/1, a_function_from_the_behaviour/1]). 7 | 8 | the_magic(M) -> 9 | M:the_magic(). 10 | 11 | a_kind_of_magic(M) -> 12 | M:a_kind_of_magic(). 13 | 14 | %% this will warn 15 | a_function_from_the_behaviour(_) -> 16 | argument_ignored. 17 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/a_behaviour_imp.erl: -------------------------------------------------------------------------------- 1 | %% @doc This module implements a local behaviour: 2 | %% The unnecessary_function_arguments rule will be ignored for all 3 | %% exported functions since we can't tell which ones are dynamic callbacks. 4 | -module(a_behaviour_imp). 5 | 6 | -behaviour(a_behaviour). 7 | 8 | -export([the_magic/0, a_kind_of_magic/1, function_with_ignored_arg/2]). 9 | 10 | the_magic() -> 11 | implemented. 12 | 13 | a_kind_of_magic(_) -> 14 | % this function won't be warned since it's a callback 15 | implemented. 16 | 17 | %% this exported function won't warn because the module implements a local behaviour 18 | function_with_ignored_arg(_, Value) -> 19 | non_exported_function_with(ignored_arg, Value). 20 | 21 | %% this should produce a warning since, being a non-exported function, it can't 22 | %% be a callback implementation. 23 | non_exported_function_with(_IgnoredArg, Value) -> 24 | Value. 25 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/clean.erl: -------------------------------------------------------------------------------- 1 | -module(clean). 2 | 3 | -define(FC(X, Y), {X, Y}). 4 | 5 | -record(r, {fc = function_call}). 6 | 7 | -export([single_fun/2, multi_fun/3]). 8 | -export([function_call/0, function_call/1, function_call/2, function_call/3]). 9 | 10 | single_fun(Arg1, Arg2) -> 11 | Arg1 * Arg2. 12 | 13 | multi_fun(_Arg1, Arg2, Arg3) when Arg2 > 1 -> 14 | [Arg2, Arg3]; 15 | multi_fun(Arg1, _, Arg3) -> 16 | [Arg1, Arg3]. 17 | 18 | function_call() -> 19 | function_call(not_a_nif). 20 | 21 | function_call(UsedArg1) -> 22 | F = function_call, 23 | F(UsedArg1, two). 24 | 25 | function_call(UsedArg1, UsedArg2) -> 26 | ?FC(UsedArg1, UsedArg2). 27 | 28 | function_call(UsedArg1, UsedArg2, vars) -> 29 | UsedArg2:UsedArg2(UsedArg1); 30 | function_call(UsedArg1, UsedArg2, macros) -> 31 | ?MODULE:function_call(UsedArg1, UsedArg2); 32 | function_call(UsedArg1, UsedArg2, records) -> 33 | (UsedArg2#r.fc)(UsedArg1); 34 | function_call(UsedArg1, UsedArg2, weird_stuff) -> 35 | begin 36 | io:format("~p~n", [UsedArg1]), 37 | function_call 38 | end(UsedArg2). 39 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/ct_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(ct_SUITE). 2 | 3 | -behavior(ct_suite). 4 | 5 | -export([all/0, init_per_suite/1, end_per_suite/1]). 6 | -export([a_test/1]). 7 | 8 | all() -> 9 | [a_test]. 10 | 11 | init_per_suite(Config) -> 12 | Config. 13 | 14 | end_per_suite(_Config) -> 15 | unused. 16 | 17 | a_test(_Config) -> 18 | unused. 19 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/export_all1.erl: -------------------------------------------------------------------------------- 1 | -module(export_all1). 2 | 3 | -behaviour(ct_suite). 4 | 5 | -compile(export_all). 6 | -compile({inline, [a_test2/1]}). 7 | 8 | all() -> [a_test]. 9 | 10 | a_test(_) -> a_test2(). 11 | 12 | a_test2() -> ok. 13 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/export_all2.erl: -------------------------------------------------------------------------------- 1 | -module(export_all2). 2 | 3 | -behaviour(ct_suite). 4 | 5 | -compile([export_all]). 6 | 7 | all() -> [a_test]. 8 | 9 | a_test(_) -> ok. 10 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/gen_server_imp.erl: -------------------------------------------------------------------------------- 1 | -module(gen_server_imp). 2 | 3 | -behaviour(gen_server). 4 | 5 | -export([init/1, handle_call/3, handle_cast/2, my_function/1, my_other_function/2]). 6 | 7 | init(_) -> 8 | ignore. 9 | 10 | handle_call(_M, _From, State) -> 11 | {noreply, State}. 12 | 13 | handle_cast(_M, State) -> 14 | {noreply, State}. 15 | 16 | my_function(_) -> 17 | this_will_warn. 18 | 19 | my_other_function(this_also_will_warn, _B) -> 20 | undefined; 21 | my_other_function(A, _B) -> 22 | A. 23 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/ignore.erl: -------------------------------------------------------------------------------- 1 | -module ignore. 2 | 3 | -export([ignore_arg2/3, ignore_arg2/2, ignore_whole_func3/3, ignore_whole_func/1, 4 | ignore_whole_func/2]). 5 | 6 | -hank([{unnecessary_function_arguments, 7 | [{ignore_arg2, 3, 2}, {ignore_arg2, 2, 1}, {ignore_whole_func3, 3}, ignore_whole_func]}]). 8 | 9 | %% Arg2 is unused but ignored 10 | ignore_arg2(Arg1, _Arg2, Arg3) -> 11 | Arg1 + Arg3. 12 | 13 | %% Arg1 and Arg2 are unused but just ignoring Arg1 for `ignore_arg2/2` 14 | ignore_arg2(_Arg1, _Arg2) -> 15 | ok. 16 | 17 | %% A multi-clause function with unused 1st param 18 | ignore_whole_func3(_, _, undefined) -> 19 | ok; 20 | ignore_whole_func3(_, Arg2, _) when is_binary(Arg2) -> 21 | Arg2; 22 | ignore_whole_func3(_, _, Arg3) -> 23 | Arg3. 24 | 25 | %% Same function names with different arities 26 | ignore_whole_func(_) -> 27 | ok. 28 | 29 | ignore_whole_func(_, _) -> 30 | ok. 31 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/ignore_config.erl: -------------------------------------------------------------------------------- 1 | -module(ignore_config). 2 | 3 | -export([ignore_arg2/3, ignore_arg2/2, ignore_whole_func3/3, ignore_whole_func/1, 4 | ignore_whole_func/2]). 5 | 6 | %% Arg2 is unused but ignored 7 | ignore_arg2(Arg1, _Arg2, Arg3) -> 8 | Arg1 + Arg3. 9 | 10 | %% Arg1 and Arg2 are unused but just ignoring Arg1 for `ignore_arg2/2` 11 | ignore_arg2(_Arg1, _Arg2) -> 12 | ok. 13 | 14 | %% A multi-clause function with unused 1st param 15 | ignore_whole_func3(_, _, undefined) -> 16 | ok; 17 | ignore_whole_func3(_, Arg2, _) when is_binary(Arg2) -> 18 | Arg2; 19 | ignore_whole_func3(_, _, Arg3) -> 20 | Arg3. 21 | 22 | %% Same function names with different arities 23 | ignore_whole_func(_) -> 24 | ok. 25 | 26 | ignore_whole_func(_, _) -> 27 | ok. 28 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/macro_behaviour_imp.erl: -------------------------------------------------------------------------------- 1 | %% @doc This module is a nasty case, it implements a behaviour inside a macro 2 | -module(macro_behaviour_imp). 3 | 4 | -define(WHY_DO_YOU_DO_THIS_TO_ME, a_behaviour). 5 | 6 | -behaviour(?WHY_DO_YOU_DO_THIS_TO_ME). 7 | 8 | -export([the_magic/0, a_kind_of_magic/1]). 9 | 10 | the_magic() -> 11 | implemented. 12 | 13 | a_kind_of_magic(_) -> 14 | % this function won't be warned since it's a callback 15 | implemented. 16 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/macros.erl: -------------------------------------------------------------------------------- 1 | -module(marcors). 2 | 3 | -export([?MODULE/1]). 4 | 5 | ?MODULE(_Something) -> 6 | ?MODULE. 7 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/nifs.erl: -------------------------------------------------------------------------------- 1 | -module(nifs). 2 | 3 | -export([hank_should_ignore_this_function_unused_params/2, and_also_this_one/3, 4 | and_this_one_too/3, even_with_multiple_clauses/2]). 5 | 6 | hank_should_ignore_this_function_unused_params(_Ignore, _Me) -> 7 | erlang:nif_error(undefined). 8 | 9 | and_also_this_one(_Ignore, Me, _Too) -> 10 | mylog:info(Me), % This line should not affect anything! 11 | erlang:nif_error(undefined, []). 12 | 13 | and_this_one_too(_Ignore, _Me, Again) -> 14 | Msg = case Again of 15 | something -> 16 | "A message!"; 17 | _ -> 18 | "Another message!" 19 | end, 20 | erlang:nif_error(Msg, [ignore, Again]). 21 | 22 | even_with_multiple_clauses(A, B) -> 23 | A + B; 24 | even_with_multiple_clauses(_, _) -> 25 | erlang:nif_error(no_warnings). 26 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/not_included_behaviour.erl: -------------------------------------------------------------------------------- 1 | -module(not_included_behaviour). 2 | 3 | -callback the_not_included() -> any(). 4 | 5 | -export([the_not_included/1]). 6 | 7 | the_not_included(M) -> 8 | M:the_not_included(). 9 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/old_ct_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(old_ct_SUITE). 2 | 3 | -export([all/0, init_per_suite/1, end_per_suite/1]). 4 | -export([a_test/1]). 5 | 6 | all() -> 7 | [a_test]. 8 | 9 | init_per_suite(Config) -> 10 | Config. 11 | 12 | end_per_suite(_Config) -> 13 | unused. 14 | 15 | a_test(_Config) -> 16 | unused. 17 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/parse_transf.erl: -------------------------------------------------------------------------------- 1 | -module(parse_transf). 2 | 3 | -export([parse_transform/2]). 4 | 5 | -spec parse_transform(Forms, Options) -> Forms 6 | when Forms :: [erl_parse:abstract_form() | erl_parse:form_info()], 7 | Options :: [compile:option()]. 8 | parse_transform(Forms, _Options) -> % Options is purposefully left unused, here. 9 | Forms. 10 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/warnings_A.erl: -------------------------------------------------------------------------------- 1 | -module(warnings_A). 2 | 3 | -export([single_fun/2, multi_fun/3, 'unicode_αβåö'/1, with_nif_stub/2]). 4 | 5 | %% _Arg2 is unused 6 | single_fun(Arg1, _Arg2) -> 7 | Arg1. 8 | 9 | %% A multi-clause function with unused 3rd param 10 | multi_fun(undefined, _, _) -> 11 | ok; 12 | multi_fun(Arg1, Arg2, _Arg3) when is_binary(Arg1) -> 13 | Arg2; 14 | multi_fun(Arg1, _, _) -> 15 | Arg1. 16 | 17 | %% Unicode should be supported and not break! 18 | 'unicode_αβåö'(_Config) -> 19 | unused_param. 20 | 21 | %% NIF stub should not affect the warning 22 | with_nif_stub(_A, B) -> 23 | {only, B, is_used}; 24 | with_nif_stub(_, _) -> 25 | erlang:nif_error('A_is_unused'). 26 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/warnings_B.erl: -------------------------------------------------------------------------------- 1 | -module(warnings_B). 2 | 3 | -export([underscore/3, not_underscored/2]). 4 | 5 | %% _ is unused 6 | underscore(_, Arg2, Arg3) -> 7 | {Arg2, Arg3}. 8 | 9 | %% Arg2 is unused and not underscored (although the linter should complain) 10 | not_underscored(Arg1, Arg2) -> 11 | Arg1. 12 | -------------------------------------------------------------------------------- /test/files/unnecessary_function_arguments/weird.erl: -------------------------------------------------------------------------------- 1 | %%% @doc This module contains weird function calls that used to crash hank 2 | -module(weird). 3 | 4 | -compile([export_all]). 5 | 6 | -record(to, {a}). 7 | 8 | record(Access) -> 9 | (Access#to.a):record(field). 10 | 11 | a_case(Statement) -> 12 | using:case Statement of 13 | to -> 14 | determine; 15 | the -> 16 | function 17 | end(). 18 | -------------------------------------------------------------------------------- /test/files/unused_callbacks/clean.erl: -------------------------------------------------------------------------------- 1 | -module(clean). 2 | 3 | -callback used_callback(map()) -> boolean(). 4 | -callback used_atom(atom()) -> list(). 5 | -callback used_in_macro() -> this | one | is | used | through | the | macro. 6 | 7 | -define(MACRO_USAGE(M), M:used_in_macro()). 8 | 9 | -export([used_callback/1, used_atom/1, used_in_macro/1]). 10 | 11 | used_callback(#{module := Module, state := State}) -> 12 | Module:used_callback(State). 13 | 14 | used_atom(Module) -> 15 | erlang:apply(Module, used_atom, [another_atom]). 16 | 17 | used_in_macro(Module) -> 18 | ?MACRO_USAGE(Module). 19 | -------------------------------------------------------------------------------- /test/files/unused_callbacks/ignore.erl: -------------------------------------------------------------------------------- 1 | -module ignore. 2 | 3 | -hank([{unused_callbacks, [all_arities, {just_one, 1}]}]). 4 | 5 | -callback all_arities() -> ignored. 6 | -callback all_arities(_) -> ignored. 7 | -callback all_arities(_, _) -> ignored. 8 | -callback just_one() -> reported. 9 | -callback just_one(_) -> ignored. 10 | -callback just_one(_, _) -> reported. 11 | -------------------------------------------------------------------------------- /test/files/unused_callbacks/ignore_config.erl: -------------------------------------------------------------------------------- 1 | -module(ignore_config). 2 | 3 | -callback all_arities() -> ignored. 4 | -callback all_arities(_) -> ignored. 5 | -callback all_arities(_, _) -> ignored. 6 | -callback just_one() -> ignored. 7 | -callback just_one(_) -> ignored. 8 | -callback just_one(_, _) -> ignored. 9 | -------------------------------------------------------------------------------- /test/files/unused_callbacks/macros.erl: -------------------------------------------------------------------------------- 1 | -module(macros). 2 | 3 | -callback used_callback() -> this | one | is | used | through | the | macro. 4 | -callback unused_callback() -> this | one | isnt | used. 5 | 6 | -define(unused_callback, {this, macro, doesnt, use, the, callback}). 7 | -define(CALLBACK, used_callback). 8 | 9 | -export([using_the_callback/1, not_using_the_callback/0]). 10 | 11 | using_the_callback(Module) -> 12 | Module:?CALLBACK(). 13 | 14 | not_using_the_callback() -> 15 | ?unused_callback. 16 | -------------------------------------------------------------------------------- /test/files/unused_callbacks/warnings.erl: -------------------------------------------------------------------------------- 1 | -module(warnings). 2 | 3 | -callback unused_callback(map()) -> boolean(). 4 | -callback undetectable_unused_callback(map()) -> boolean(). 5 | 6 | -export([unused_callback/1, call_undetectable/1]). 7 | 8 | %% This function doesn't count as callback usage because 9 | %% it's just a regular module function that happens to have 10 | %% the same name as the callback definition, but it may have 11 | %% nothing to do with the callback itself like in this example 12 | %% in where `other_module:call/2` is "opaque" to us and its 13 | %% implementation may not use this module callbacks at all 14 | unused_callback(#{module := Module, state := State}) -> 15 | other_module:call(Module, State). 16 | 17 | undetectable_unused_callback(State) -> 18 | other_module:do(State). 19 | 20 | call_undetectable(#{state := State}) -> 21 | undetectable_unused_callback(State). 22 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app0-with-other-name/include/header.hrl: -------------------------------------------------------------------------------- 1 | -define(APP0_HEADER, "this is a header from app0 that might be unused"). 2 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app0-with-other-name/src/app0.app.src: -------------------------------------------------------------------------------- 1 | {application, app0, [{vsn, "1.0.1"}]}. 2 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app1/include/header.hrl: -------------------------------------------------------------------------------- 1 | -define(APP1_HEADER, "this is header from app1 that might be unused"). 2 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app1/src/app1.app.src: -------------------------------------------------------------------------------- 1 | {application, app1, [{vsn, "0"}]}. 2 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app1/src/app1_include.erl: -------------------------------------------------------------------------------- 1 | -module(app1_include). 2 | 3 | -include("header.hrl"). 4 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app1/src/app1_include_lib.erl: -------------------------------------------------------------------------------- 1 | -module(app1_include_lib). 2 | 3 | -include_lib("app1/include/header.hrl"). 4 | -include_lib("app0/include/header.hrl"). 5 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app1/src/app1_not_using_header.erl: -------------------------------------------------------------------------------- 1 | -module(app1_not_using_header). 2 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app2/src/app2.app.src: -------------------------------------------------------------------------------- 1 | {application, app2, [{vsn, "0"}]}. 2 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app2/src/app2_include.erl: -------------------------------------------------------------------------------- 1 | -module(app2_include). 2 | 3 | -include("../app1/include/header.hrl"). 4 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app2/src/app2_include_lib.erl: -------------------------------------------------------------------------------- 1 | -module(app2_include_lib). 2 | 3 | -include_lib("app1/include/header.hrl"). 4 | -include_lib("app0/include/header.hrl"). 5 | -------------------------------------------------------------------------------- /test/files/unused_hrls/lib/app2/src/app2_not_using_header.erl: -------------------------------------------------------------------------------- 1 | -module(app2_not_using_header). 2 | -------------------------------------------------------------------------------- /test/files/unused_ignores/ignore_all.erl: -------------------------------------------------------------------------------- 1 | -module(ignore_all). 2 | -hank ignore. 3 | -------------------------------------------------------------------------------- /test/files/unused_ignores/unused_ignores.erl: -------------------------------------------------------------------------------- 1 | -module(unused_ignores). 2 | 3 | -hank([{unnecessary_function_arguments, [{non_exported_function_with, 2, 1}]}]). 4 | -hank([{unused_macros, ["MACRO", "MICRO"]}]). 5 | -hank([bad_rule]). 6 | -------------------------------------------------------------------------------- /test/files/unused_macros/double.erl: -------------------------------------------------------------------------------- 1 | -module(double). 2 | 3 | -ifdef(X). 4 | 5 | -define(Y, y). 6 | 7 | -else. 8 | 9 | -define(Y, z). 10 | 11 | -endif. 12 | -------------------------------------------------------------------------------- /test/files/unused_macros/flow.erl: -------------------------------------------------------------------------------- 1 | -module(flow). 2 | 3 | -export([iif/0]). 4 | 5 | -ifndef(IFNDEF). 6 | -define(IFNDEF, true). 7 | -endif. 8 | 9 | -define(IFDEF, true). 10 | -ifdef(IFDEF). 11 | -type ifdef() :: ifdef. 12 | -export_type([ifdef/0]). 13 | -endif. 14 | 15 | -define(IF(X), not X). 16 | 17 | -if(?IF(?VALUE)). 18 | 19 | iif() -> true. 20 | 21 | -elif(not ?IF(?VALUE)). 22 | 23 | iif() -> false. 24 | 25 | -else. 26 | 27 | iif() -> wat. 28 | 29 | -endif. 30 | 31 | -define(UNDEF, undef). 32 | -undef(UNDEF). 33 | -------------------------------------------------------------------------------- /test/files/unused_macros/header.hrl: -------------------------------------------------------------------------------- 1 | %% This file contains all the macros that are used or unused in the following modules: 2 | %% - unused_macro_sample.erl 3 | %% - used_macro_sample.erl 4 | %% 5 | %% Even when those files don't include this header, Hank will only report the 6 | %% strictly unused macros as unused. It will act conservatively and not make any 7 | %% effort to actually verify where each macro that's used is defined. 8 | %% That's too hard. And if you have a project with multiple definitions of the 9 | %% same macro with the same arity... well... as long as one of them is used, 10 | %% none of them will be reported. Sorry. 11 | -define(SIMPLE_MACRO, simple_macro). 12 | -define(MACRO_USED_IN(MACRO), ??MACRO). 13 | -define(MACRO_THAT_USES(OTHER_MACRO), {OTHER_MACRO, ?MACRO_USED_IN(?OTHER_MACRO)}). 14 | -define(USED_MACROS_AS_FUNC, ?MODULE:simple_macros_func). 15 | -define(UNUSED_MACRO, unused_macro). 16 | -define(UNUSED_MACRO_WITH(), no_arguments). 17 | -define(UNUSED_MACRO_WITH(ARGUMENTS), unused_macro_with(ARGUMENTS)). 18 | -define(UNUSED_MACRO_WITH(TWO, ARGUMENTS), unused_macro_with(TWO, ARGUMENTS)). 19 | 20 | -define( UNUSED_MACRO_WITH_BROKEN( CODE ) , case ?CODE of code -> ??CODE ) . 21 | 22 | -define(UNUSED_MACRO_UNICODE_ÇØÍ, unused_macro_unicode_ÇØÍ). 23 | %% This one is actually used, since it's one of used_macro_sample macros, too. 24 | -define(macroIsAnAtom, macro_is_an_atom). 25 | -------------------------------------------------------------------------------- /test/files/unused_macros/ignore.erl: -------------------------------------------------------------------------------- 1 | -module ignore. 2 | 3 | -hank([{unused_macros, 4 | ["MACRO_ALL", {"MACRO_0", 0}, {"MACRO_1", 1}, {"MACRO_NONE", none}]}]). 5 | 6 | -define(MACRO_ALL, "this macro is always ignored"). 7 | -define(MACRO_ALL(), "regardless of its arity"). 8 | -define(MACRO_ALL(It), "is never reported as unused"). 9 | -define(MACRO_ALL(Not, Even), "if it has multiple arguments"). 10 | -define(MACRO_0, "This version of the macro should not be ignored"). 11 | -define(MACRO_0(), "This one should since it has 0 arguments"). 12 | -define(MACRO_0(And), "this one should be reported since it has one"). 13 | -define(MACRO_0(Also, This), "one, that has 2"). 14 | -define(MACRO_1, "For this macro"). 15 | -define(MACRO_1(), "there should be a report"). 16 | -define(MACRO_1(But), "not for this version with arity 1"). 17 | -define(MACRO_1(Just, For), "all the others"). 18 | -define(MACRO_NONE, "This instance should be ignored"). 19 | -define(MACRO_NONE(), "But there should be a report"). 20 | -define(MACRO_NONE(For), "each of the"). 21 | -define(MACRO_NONE(Other, Versions), ""). 22 | -------------------------------------------------------------------------------- /test/files/unused_macros/ignore_config.erl: -------------------------------------------------------------------------------- 1 | -module(ignore_config). 2 | 3 | %% @doc No warnings should be emitted for this file since 4 | %% rebar.config specifically states that all of them 5 | %% should be ignored. 6 | 7 | -define(MACRO_ALL, "this macro is always ignored"). 8 | -define(MACRO_ALL(), "regardless of its arity"). 9 | -define(MACRO_ALL(It), "is never reported as unused"). 10 | -define(MACRO_ALL(Not, Even), "if it has multiple arguments"). 11 | -define(MACRO_0(), "This one should since it has 0 arguments"). 12 | -define(MACRO_1(But), "not for this version with arity 1"). 13 | -define(MACRO_NONE, "This instance should be ignored"). 14 | -------------------------------------------------------------------------------- /test/files/unused_macros/unused_macro_sample.erl: -------------------------------------------------------------------------------- 1 | -module(unused_macro_sample). 2 | 3 | -compile(export_all). 4 | 5 | -define(UNUSED_MACRO, unused_macro). 6 | -define(UNUSED_MACRO_WITH(), no_arguments). 7 | -define(UNUSED_MACRO_WITH(ARGUMENTS), unused_macro_with(ARGUMENTS)). 8 | -define(UNUSED_MACRO_WITH(TWO, ARGUMENTS), unused_macro_with(TWO, ARGUMENTS)). 9 | 10 | %% hank won't detect this one, since it's unparseable [https://github.com/AdRoll/rebar3_hank/issues/37] 11 | -define( UNUSED_MACRO_WITH_BROKEN( CODE ) , case ?CODE of code -> ??CODE ) . 12 | 13 | %% Unicode should be supported and not break! 14 | -define(UNUSED_MACRO_UNICODE_ÇØÍ, unused_macro_unicode_ÇØÍ). 15 | -define(macroIsAnAtom, macro_is_an_atom). 16 | -------------------------------------------------------------------------------- /test/files/unused_macros/used_macro_sample.erl: -------------------------------------------------------------------------------- 1 | -module(used_macro_sample). 2 | 3 | -compile(export_all). 4 | 5 | -define(SIMPLE_MACRO, simple_macro). 6 | -define(MACRO_USED_IN(MACRO), ??MACRO). 7 | -define(MACRO_THAT_USES(OTHER_MACRO), {OTHER_MACRO, ?MACRO_USED_IN(?OTHER_MACRO)}). 8 | -define(USED_MACROS_AS_FUNC, ?MODULE:simple_macros_func). 9 | 10 | -define( USED_MACRO_WITH_BROKEN( CODE ) , case ?CODE of code -> ??CODE ) . 11 | 12 | -define(macroIsAnAtom, macro_is_an_atom). 13 | -define(macroIsAnAtom(With), {macro_is_an_atom, With}). 14 | -define(macroIsAnAtomToo(), macro_is_an_atom). 15 | 16 | using_macro(NotAMacro) -> 17 | ?MACRO_THAT_USES(NotAMacro). 18 | 19 | simple_macro() -> 20 | ?SIMPLE_MACRO. 21 | 22 | broken( ) -> ?USED_MACRO_WITH_BROKEN( SIMPLE_MACRO ) ; _ -> other end . 23 | 24 | macro_is_an_atom() -> 25 | {?macroIsAnAtom, ?macroIsAnAtom(?macroIsAnAtomToo())}. 26 | 27 | simple_macros_call() -> 28 | ?USED_MACROS_AS_FUNC(<<"">>, <<"">>). 29 | 30 | simple_macros_func(Data0, Data1) -> 31 | io:format("~p~n", [{Data0, Data1}]). 32 | -------------------------------------------------------------------------------- /test/files/unused_record_fields/header.hrl: -------------------------------------------------------------------------------- 1 | %% This file contains a record with all the fields that are used or unused 2 | %% in unused_record_field_sample.erl 3 | %% 4 | %% Even when the module doesn't include this header, Hank will only report the 5 | %% strictly unused record fields as unused. It will act conservatively and not 6 | %% make any effort to actually verify where each record field that's used is 7 | %% defined. 8 | %% That's too hard. And if you have a project with multiple definitions of the 9 | %% same record with the same fields... well... as long as one of them is used, 10 | %% none of them will be reported. Sorry. 11 | -record(a_record, 12 | {used_field, 13 | used_typed_field :: used_typed_field, 14 | used_field_with_default = used_field_with_default, 15 | used_typed_field_with_default = used_typed_field_with_default :: 16 | used_typed_field_with_default, 17 | unused_field, 18 | unused_typed_field :: unused_typed_field, 19 | unused_field_with_default = unused_field_with_default, 20 | unused_typed_field_with_default = unused_typed_field_with_default :: 21 | unused_typed_field_with_default}). 22 | -record(really_unused_record, {really_unused_field}). 23 | -------------------------------------------------------------------------------- /test/files/unused_record_fields/ignore.erl: -------------------------------------------------------------------------------- 1 | -module ignore. 2 | 3 | -compile(export_all). 4 | 5 | -hank([{unused_record_fields, 6 | [ignored_record, {a_record, ignored_field_1}, {a_record, ignored_field_2}]}]). 7 | 8 | -record(a_record, {used_field, unused_field, ignored_field_1, ignored_field_2}). 9 | -record(ignored_record, {used_field, unused_field, ignored_field_1, ignored_field_2}). 10 | 11 | usage() -> 12 | #a_record{used_field = #ignored_record{used_field = used}}. 13 | -------------------------------------------------------------------------------- /test/files/unused_record_fields/ignore_config.erl: -------------------------------------------------------------------------------- 1 | -module ignore. 2 | 3 | -compile(export_all). 4 | 5 | %% @doc No warnings should be emitted for this file since 6 | %% rebar.config specifically states that all of them 7 | %% should be ignored. 8 | 9 | -record(a_record, {used_field, ignored_field1, ignored_field2}). 10 | -record(ignored_record, {used_field, unused_field, ignored_field_1, ignored_field_2}). 11 | 12 | usage() -> 13 | #a_record{used_field = #ignored_record{used_field = used}}. 14 | -------------------------------------------------------------------------------- /test/files/unused_record_fields/macros.erl: -------------------------------------------------------------------------------- 1 | -module(macros). 2 | 3 | %% There must be a paresable record 4 | -record(a_rec, {unused_field}). 5 | %% But also an "unparseable" one 6 | -record(?MODULE, {}). 7 | -------------------------------------------------------------------------------- /test/files/unused_record_fields/unused_record_field_sample.erl: -------------------------------------------------------------------------------- 1 | -module(unused_record_field_sample). 2 | 3 | -compile(export_all). 4 | 5 | -record(a_record, 6 | {used_field, 7 | used_typed_field :: used_typed_field, 8 | used_field_with_default = used_field_with_default, 9 | used_typed_field_with_default = used_typed_field_with_default :: 10 | used_typed_field_with_default, 11 | unused_field, 12 | unused_typed_field :: unused_typed_field, 13 | unused_field_with_default = unused_field_with_default, 14 | unused_typed_field_with_default = unused_typed_field_with_default :: 15 | unused_typed_field_with_default}). 16 | %% Unicode should be supported and not break! 17 | -record('unicode_αåβö', {'attr_αåβö' :: a_type()}). 18 | 19 | %% This doesn't count as usage 20 | -type a_type() :: 21 | #a_record{unused_field :: unused_field, 22 | unused_field_with_default :: unused_field_with_default}. 23 | 24 | %% This doesn't count as usage 25 | construct() -> 26 | #a_record{used_field = used_field, used_typed_field = used_typed_field}. 27 | 28 | %% This doesn't count as usage either 29 | update(R) -> 30 | R#a_record{used_field = used_field, used_typed_field = used_typed_field}. 31 | 32 | %% This counts as usage 33 | pattern_match(#a_record{used_field = UF}, R) -> 34 | #a_record{used_field_with_default = UFWD} = R, 35 | [UF, UFWD]. 36 | 37 | %% This counts as usage 38 | index(Rs) -> 39 | lists:keysort(#a_record.used_typed_field, Rs). 40 | 41 | %% This counts as usage, too 42 | retrieve(R) -> 43 | R#a_record.used_typed_field_with_default. 44 | -------------------------------------------------------------------------------- /test/files/unused_record_fields/used_record_field_sample.erl: -------------------------------------------------------------------------------- 1 | -module(used_record_field_sample). 2 | 3 | -compile(export_all). 4 | 5 | -record(?MODULE, 6 | {this, record, is, not_used, but, it, cant, be, parsed, so, its, not_analyzed}). 7 | -record(a_record, 8 | {used_field, 9 | used_typed_field :: used_typed_field, 10 | used_field_with_default = used_field_with_default, 11 | used_typed_field_with_default = used_typed_field_with_default :: 12 | used_typed_field_with_default, 13 | another_used_field}). 14 | 15 | %% This doesn't count as usage 16 | -type a_type() :: #a_record{}. 17 | 18 | %% This doesn't count as usage 19 | construct() -> 20 | #a_record{used_field = used_field, used_typed_field = used_typed_field}. 21 | 22 | %% This doesn't count as usage either 23 | update(R) -> 24 | R#a_record{used_field = used_field, used_typed_field = used_typed_field}. 25 | 26 | %% This counts as usage 27 | pattern_match(#a_record{used_field = UF} = R) -> 28 | #a_record{used_field_with_default = UFWD} = R, 29 | [UF, UFWD]. 30 | 31 | %% This counts as usage 32 | index(Rs) -> 33 | lists:keysort(#a_record.used_typed_field, Rs). 34 | 35 | %% This counts as usage, too 36 | retrieve(R) -> 37 | R#a_record.used_typed_field_with_default. 38 | 39 | %% This counts as usage for all fields 40 | use_all_fields(R) -> 41 | R#a_record{_ = all_used}. 42 | -------------------------------------------------------------------------------- /test/global_rejector.erl: -------------------------------------------------------------------------------- 1 | %%% @doc This module is used by test_app_SUITE 2 | -module(global_rejector). 3 | 4 | -behaviour(hank_rule). 5 | 6 | -export([analyze/2, ignored/2]). 7 | 8 | %% @doc All files are wrong!! 9 | analyze(ASTs, _) -> 10 | [#{file => File, 11 | line => 1, 12 | text => "global_rejector", 13 | pattern => undefined} 14 | || {File, _} <- ASTs]. 15 | 16 | -spec ignored(hank_rule:ignore_pattern(), term()) -> boolean(). 17 | ignored(_Pattern, _IgnoreSpec) -> 18 | false. 19 | -------------------------------------------------------------------------------- /test/hank_test_utils.erl: -------------------------------------------------------------------------------- 1 | -module(hank_test_utils). 2 | 3 | -export([init_per_testcase/2, end_per_testcase/1]). 4 | -export([init/0, init/1, mock_context/2, analyze_and_sort/2, analyze_and_sort/3, 5 | set_cwd/1, abs_test_path/1]). 6 | 7 | init_per_testcase(Config, TestDirName) -> 8 | {ok, Cwd} = file:get_cwd(), % Keep the original working directory 9 | set_cwd(TestDirName), 10 | [{cwd, Cwd} | Config]. 11 | 12 | end_per_testcase(Config) -> 13 | {value, {cwd, Cwd}, NewConfig} = lists:keytake(cwd, 1, Config), 14 | ok = file:set_cwd(Cwd), 15 | NewConfig. 16 | 17 | %% @doc Initialize rebar3 to simulate running `rebar3 hank` 18 | init() -> 19 | {ok, State} = 20 | rebar3_hank:init( 21 | rebar_state:new()), 22 | State. 23 | 24 | init(AppName) when is_atom(AppName) -> 25 | RebarAppInfo = 26 | rebar_app_info:name( 27 | rebar_app_info:new(), atom_to_binary(AppName, utf8)), 28 | RebarAppInfo2 = rebar_app_info:dir(RebarAppInfo, abs_test_path(atom_to_list(AppName))), 29 | rebar_state:project_apps(init(), RebarAppInfo2). 30 | 31 | mock_context(Apps, ProjectApps) -> 32 | AppsAbs = maps:map(fun(_App, Path) -> filename:absname(Path) end, Apps), 33 | hank_context:new(AppsAbs, ProjectApps). 34 | 35 | analyze_and_sort(Files, Rules) -> 36 | analyze_and_sort(Files, Rules, mock_context(#{}, [])). 37 | 38 | analyze_and_sort(Files, Rules, Context) when is_map(Context) -> 39 | analyze_and_sort(Files, [], Rules, Context); 40 | analyze_and_sort(Files, IgnoreSpecs, Rules) -> 41 | analyze_and_sort(Files, IgnoreSpecs, Rules, mock_context(#{}, [])). 42 | 43 | analyze_and_sort(Files, IgnoreSpecs, Rules, Context) -> 44 | ParsingStyle = 45 | case erlang:monotonic_time(millisecond) rem 2 of 46 | 0 -> 47 | parallel; 48 | _ -> 49 | sequential 50 | end, 51 | #{stats := Stats, 52 | unused_ignores := UnusedIgnores, 53 | results := Results} = 54 | hank:analyze(Files, IgnoreSpecs, Rules, ParsingStyle, Context), 55 | #{parsing := Parsing, 56 | analyzing := Analyzing, 57 | total := Total} = 58 | Stats, 59 | {true, Stats} = {Parsing >= 0, Stats}, 60 | {true, Stats} = {Analyzing >= 0, Stats}, 61 | {true, Stats} = {Parsing + Analyzing =< Total, Stats}, 62 | #{unused_ignores => lists:sort(UnusedIgnores), results => lists:sort(Results)}. 63 | 64 | set_cwd(RelativePathOrFilename) -> 65 | ok = file:set_cwd(abs_test_path(RelativePathOrFilename)). 66 | 67 | abs_test_path(FilePath) -> 68 | filename:join( 69 | code:lib_dir(rebar3_hank), "test/files/" ++ FilePath). 70 | -------------------------------------------------------------------------------- /test/hidden_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Test module for the hidden files/folders functionality 2 | -module(hidden_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([hidden/1]). 6 | 7 | all() -> 8 | [hidden]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "hidden"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc No warning should be emitted for files that are hidden or that are 17 | %% included in hidden folders. 18 | hidden(_Config) -> 19 | State = hank_test_utils:init(), 20 | 21 | {ok, _} = rebar3_hank_prv:do(State), 22 | 23 | {comment, ""}. 24 | -------------------------------------------------------------------------------- /test/ignore_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Test module for the ignore functionality 2 | -module(ignore_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([rebar_config/1, hank_ignore/1, hank_individual_rules/1, rebar_config_ignore/1]). 6 | 7 | -elvis([{elvis_style, dont_repeat_yourself, disable}]). % for rebar_config_ignore/1 8 | 9 | all() -> 10 | [rebar_config, hank_ignore, hank_individual_rules, rebar_config_ignore]. 11 | 12 | init_per_testcase(_, Config) -> 13 | hank_test_utils:init_per_testcase(Config, "ignore"). 14 | 15 | end_per_testcase(_, Config) -> 16 | hank_test_utils:end_per_testcase(Config). 17 | 18 | %% @doc No warning should be emitted for files listed in the ignored part of 19 | %% hank's config at rebar.config 20 | rebar_config(_Config) -> 21 | State = hank_test_utils:init(), 22 | 23 | ct:comment("Warnings should be emitted since we're not ignoring the problematic " 24 | "files"), 25 | State1 = rebar_state:set(State, hank, []), 26 | Warnings = find_warnings(State1), 27 | 28 | ct:comment("If we ignore the problematic files, we should not get warnings " 29 | "for them"), 30 | State2 = 31 | rebar_state:set(State, 32 | hank, 33 | [{ignore, [binary_to_list(File) || #{file := File} <- Warnings]}]), 34 | {ok, _} = rebar3_hank_prv:do(State2), 35 | 36 | ct:comment("If we ignore some rules on the problematic files, we should " 37 | "not get warnings for them"), 38 | State3 = 39 | rebar_state:set(State, 40 | hank, 41 | [{ignore, 42 | [{binary_to_list(File), global_rejector} 43 | || #{file := File, text := <<" global_rejector">>} <- Warnings]}]), 44 | Warnings3 = find_warnings(State3), 45 | [] = [x || #{text := <<" global_rejector">>} <- Warnings3], 46 | true = length(Warnings3) > 0, 47 | [] = Warnings3 -- Warnings, 48 | true = length(Warnings -- Warnings3) > 0, 49 | 50 | {comment, ""}. 51 | 52 | %% @doc No warning should be emitted for files with -hank ignore 53 | hank_ignore(_Config) -> 54 | %% Initialize rebar3 state as if we run `rebar3 hank` with the default rules 55 | State = hank_test_utils:init(), 56 | 57 | ct:comment("With -hank ignore, there should be no warnings"), 58 | Warnings = find_warnings(State), 59 | [] = 60 | [Warning || Warning = #{file := File} <- Warnings, string:equal(File, "with_ignore.erl")], 61 | [] = 62 | [Warning || Warning = #{file := File} <- Warnings, string:equal(File, "with_ignore.hrl")], 63 | 64 | {comment, ""}. 65 | 66 | %% @doc No warning should be emitted for rules ignored with -hank [rule, ...] 67 | hank_individual_rules(_Config) -> 68 | State = hank_test_utils:init(), 69 | 70 | ct:comment("With -hank ignore, there should only be warnings for non-ignored " 71 | "rules"), 72 | Rules = [unused_macros, unnecessary_function_arguments, global_rejector], 73 | State1 = rebar_state:set(State, hank, [{rules, Rules}]), 74 | Warnings = find_warnings(State1), 75 | [<<" global_rejector">>] = 76 | [Text 77 | || #{file := File, text := Text} <- Warnings, string:equal(File, "specific_ignore.erl")], 78 | 79 | {comment, ""}. 80 | 81 | %% @doc No warning should be emitted for lists of ignored rules and neither should 82 | %% evaluation of the code fail 83 | rebar_config_ignore(_Config) -> 84 | Rule1 = unused_macros, 85 | Rule2 = unnecessary_function_arguments, 86 | FileErl = "rebar_config_ignore.erl", 87 | Rules = [Rule1, Rule2], 88 | 89 | State0 = hank_test_utils:init(), 90 | State1 = rebar_state:set(State0, hank, []), 91 | 92 | ct:comment("Prepare for the next test (we start with whatever worked)"), 93 | State2 = 94 | rebar_state:set(State1, 95 | hank, 96 | [{rules, Rules}, {ignore, [{FileErl, Rule1}, {FileErl, Rule2}]}]), 97 | Warnings0 = find_warnings(State2), 98 | [] = [Warning0 || Warning0 = #{file := File} <- Warnings0, string:equal(File, FileErl)], 99 | 100 | ct:comment("Test with an ignore list"), 101 | State3 = 102 | rebar_state:set(State2, hank, [{rules, Rules}, {ignore, [{FileErl, [Rule1, Rule2]}]}]), 103 | Warnings1 = find_warnings(State3), 104 | [] = [Warning1 || Warning1 = #{file := File} <- Warnings1, string:equal(File, FileErl)], 105 | 106 | {comment, ""}. 107 | 108 | find_warnings(State) -> 109 | {error, Error} = rebar3_hank_prv:do(State), 110 | <<"The following pieces of code", 111 | " are dead and should be removed:\n", 112 | ResultsBin/binary>> = 113 | iolist_to_binary(Error), 114 | Results = binary:split(ResultsBin, <<$\n>>, [global, trim]), 115 | 116 | lists:map(fun(Result) -> 117 | [File, Line | Text] = binary:split(Result, <<$:>>, [global, trim]), 118 | #{file => File, 119 | line => Line, 120 | text => iolist_to_binary(Text)} 121 | end, 122 | Results). 123 | -------------------------------------------------------------------------------- /test/single_use_hrl_attrs_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Tests for the single_use_hrl_attrs rule 2 | -module(single_use_hrl_attrs_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([hrl_in_just_one_module/1, ignore_config/1]). 6 | 7 | all() -> 8 | [hrl_in_just_one_module, ignore_config]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "single_use_hrl_attrs"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc Hank finds hrl attributes used in just one module 17 | hrl_in_just_one_module(_) -> 18 | Files = filelib:wildcard("**/*.[he]rl"), 19 | [#{file := "lib/app/include/flow.hrl", 20 | text := <<"?IFNDEF is used only at lib/app/src/flow.erl">>}, 21 | #{file := "lib/app/include/flow.hrl", 22 | text := <<"?IFDEF is used only at lib/app/src/flow.erl">>}, 23 | #{file := "lib/app/include/flow.hrl", 24 | text := <<"?IF/1 is used only at lib/app/src/flow.erl">>}, 25 | #{file := "lib/app/include/flow.hrl", 26 | text := <<"?UNDEF is used only at lib/app/src/flow.erl">>}, 27 | #{file := "lib/app/include/header1.hrl", 28 | text := <<"?APP_HEADER_1 is used only at lib/app/src/app_include_lib.erl">>}, 29 | #{file := "lib/app/include/header1.hrl", 30 | text := <<"?SOME_MACRO_1/1 is used only at lib/app/src/app_include_lib.erl">>}, 31 | #{file := "lib/app/include/header1.hrl", 32 | text := <<"?SOME_DEFINE is used only at lib/app/src/app_other.erl">>}, 33 | #{file := "lib/app/include/header2.hrl", 34 | text := <<"?APP_HEADER_2 is used only at lib/app/src/app_include.erl">>}, 35 | #{file := "lib/app/include/header2.hrl", 36 | text := <<"?SOME_MACRO_2/1 is used only at lib/app/src/app_include.erl">>}, 37 | #{file := "lib/app/include/header3.hrl", 38 | text := <<"#a_record is used only at lib/app/src/app_other.erl">>}, 39 | #{file := "lib/app/include/header3.hrl", 40 | text := <<"#another_record is used only at lib/app/src/app_other.erl">>}, 41 | #{file := "lib/app/include/header3.hrl", 42 | text := <<"#'unicode_αåβö' is used only at lib/app/src/app_other.erl"/utf8>>}, 43 | #{file := "lib/app/include/ignore.hrl", 44 | text := <<"?MACRO_0 is used only at lib/app/src/ignore.erl">>}, 45 | #{file := "lib/app/include/ignore.hrl", 46 | text := <<"?MACRO_0/1 is used only at lib/app/src/ignore.erl">>}, 47 | #{file := "lib/app/include/ignore.hrl", 48 | text := <<"?MACRO_0/2 is used only at lib/app/src/ignore.erl">>}, 49 | #{file := "lib/app/include/ignore.hrl", 50 | text := <<"?MACRO_1 is used only at lib/app/src/ignore.erl">>}, 51 | #{file := "lib/app/include/ignore.hrl", 52 | text := <<"?MACRO_1/0 is used only at lib/app/src/ignore.erl">>}, 53 | #{file := "lib/app/include/ignore.hrl", 54 | text := <<"?MACRO_1/2 is used only at lib/app/src/ignore.erl">>}, 55 | #{file := "lib/app/include/ignore.hrl", 56 | text := <<"?MACRO_NONE/0 is used only at lib/app/src/ignore.erl">>}, 57 | #{file := "lib/app/include/ignore.hrl", 58 | text := <<"?MACRO_NONE/1 is used only at lib/app/src/ignore.erl">>}, 59 | #{file := "lib/app/include/ignore.hrl", 60 | text := <<"?MACRO_NONE/2 is used only at lib/app/src/ignore.erl">>}] = 61 | analyze(Files), 62 | ok. 63 | 64 | %% @doc No warnings since rebar.config specifically states that all of them 65 | %% should be ignored. 66 | ignore_config(_) -> 67 | File = "lib/app/include/header3.hrl", 68 | Files = [File, "lib/app/src/app_include.erl"], 69 | IgnoreSpecs = [{File, single_use_hrl_attrs, ["SOME_MACRO_3", "APP_HEADER_3"]}], 70 | #{results := []} = 71 | hank_test_utils:analyze_and_sort(Files, IgnoreSpecs, [single_use_hrl_attrs]). 72 | 73 | analyze(Files) -> 74 | Apps = #{app0 => "lib/app"}, 75 | Context = hank_test_utils:mock_context(Apps, [app0]), 76 | #{results := Results, unused_ignores := []} = 77 | hank_test_utils:analyze_and_sort(Files, [single_use_hrl_attrs], Context), 78 | Results. 79 | -------------------------------------------------------------------------------- /test/single_use_hrls_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(single_use_hrls_SUITE). 2 | 3 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 4 | -export([only_hrls/1, single_use/1, respects_ignore/1, ignores_missing_files/1, 5 | alltogether/1]). 6 | 7 | all() -> 8 | [only_hrls, single_use, respects_ignore, ignores_missing_files, alltogether]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "single_use_hrls"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc Hank doesn't find anything to report for header files only 17 | only_hrls(_) -> 18 | ct:comment("If there are no Erlang files, it should not detect anything."), 19 | OnlyHrls = ["include/multi.hrl", "include/single.hrl"], 20 | [] = analyze(OnlyHrls), 21 | ok. 22 | 23 | %% @doc Hank finds header files that are only included in just one module 24 | single_use(_) -> 25 | ct:comment("It should detect include/single.hrl because it's only included " 26 | "at src/include_single.erl"), 27 | Files = 28 | ["include/multi.hrl", 29 | "include/single.hrl", 30 | "src/include_multi.erl", 31 | "src/include_single.erl"], 32 | [#{file := "include/single.hrl", 33 | text := <<"This header file is only included at: src/include_single.erl">>}] = 34 | analyze(Files), 35 | ok. 36 | 37 | %% @doc Hank respects the `ignore` attribute in a header file and at `rebar.config` 38 | respects_ignore(_) -> 39 | ct:comment("It should not detect include/ignored.hrl because is ignored " 40 | "although it's only included at src/include_ignored.erl"), 41 | Files = 42 | ["include/multi.hrl", 43 | "include/single.hrl", 44 | "include/ignored.hrl", 45 | "src/include_multi.erl", 46 | "src/include_single.erl", 47 | "src/include_ignored.erl"], 48 | [] = analyze(Files, [{"include/single.hrl", all, all}]), 49 | ok. 50 | 51 | %% @doc Hank ignores header files that aren't present in the list of analyzed files 52 | ignores_missing_files(_) -> 53 | ct:comment("It should not detect missing.hrl because it isn't present in " 54 | "the list of files analyzed by Hank, although is only included " 55 | "at src/include_missing.erl"), 56 | Files = ["include/multi.hrl", "src/include_multi.erl", "src/include_missing.erl"], 57 | [] = analyze(Files), 58 | ok. 59 | 60 | %% @doc Hank finds and ignores accordingly 61 | alltogether(_) -> 62 | ct:comment("It should detect include/single.hrl because it's only included " 63 | "at src/include_single.erl and not detect include/ignored.hrl"), 64 | Files = 65 | ["include/multi.hrl", 66 | "include/single.hrl", 67 | "include/single_unicode.hrl", 68 | "include/ignored.hrl", 69 | "src/include_multi.erl", 70 | "src/include_single.erl", 71 | "src/include_ignored.erl", 72 | "src/include_missing.erl" 73 | | filelib:wildcard("src/include_unicode_*.erl")], 74 | [#{file := "include/single.hrl", 75 | text := <<"This header file is only included at: src/include_single.erl">>}, 76 | #{file := "include/single_unicode.hrl", 77 | text := 78 | <<"This header file is only included at: src/include_unicode_"/utf8, _/binary>>}] = 79 | analyze(Files), 80 | ok. 81 | 82 | analyze(Files) -> 83 | analyze(Files, []). 84 | 85 | analyze(Files, IgnoredFiles) -> 86 | #{results := Results, unused_ignores := []} = 87 | hank_test_utils:analyze_and_sort(Files, IgnoredFiles, [single_use_hrls]), 88 | Results. 89 | -------------------------------------------------------------------------------- /test/test_app_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Test module for the app in general 2 | -module(test_app_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([with_warnings/1, without_warnings/1]). 6 | 7 | all() -> 8 | [with_warnings, without_warnings]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "test_app"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc In a project where there are things to report, hank should return error 17 | with_warnings(_Config) -> 18 | State = hank_test_utils:init(), 19 | ct:comment("With default rules, there should be warnings since " 20 | ++ "hank_rule:default_rules() should find global_rejector"), 21 | State1 = rebar_state:set(State, hank, []), 22 | find_warnings(State1), 23 | 24 | ct:comment("If we alter the equivalent to rebar.config's hank to use a " 25 | "global rejector rule, it should find the same warnings"), 26 | State2 = rebar_state:set(State, hank, [{rules, [global_rejector]}]), 27 | find_warnings(State2), 28 | 29 | {comment, ""}. 30 | 31 | %% @doc In a project where all rules run cleanly, hank should return OK 32 | without_warnings(_Config) -> 33 | State = hank_test_utils:init(), 34 | 35 | ct:comment("With no rules, there should be no warnings"), 36 | State2 = rebar_state:set(State, hank, [{rules, []}]), 37 | {ok, _} = rebar3_hank_prv:do(State2), 38 | 39 | ct:comment("Without the global rejector, there should be no warnings either"), 40 | Rules = hank_rule:default_rules() -- [global_rejector], 41 | State3 = hank_test_utils:init(test_app), 42 | IgnorePattern = 43 | {ignore, [{"sys.config", unused_configuration_options, [not_used_but_ignored]}]}, 44 | State4 = rebar_state:set(State3, hank, [{rules, Rules}, IgnorePattern]), 45 | {ok, _} = rebar3_hank_prv:do(State4), 46 | 47 | {comment, ""}. 48 | 49 | find_warnings(State) -> 50 | %% Run hank 51 | {error, Error} = rebar3_hank_prv:do(State), 52 | <<"The following pieces of code", 53 | " are dead and should be removed:\n", 54 | ResultsBin/binary>> = 55 | iolist_to_binary(Error), 56 | Results = binary:split(ResultsBin, <<$\n>>, [global, trim]), 57 | 58 | %% There are at least 8 files in the test_app folder. 59 | %% We might add more in the future and we don't want this test to fail 60 | %% just because of that. 61 | true = 8 =< length(Results), 62 | lists:foreach(fun(Result) -> 63 | %% each result looks like path/to/file:#: msg 64 | [_, <<"1">>, <<" global_rejector">>] = 65 | binary:split(Result, <<$:>>, [global, trim]) 66 | end, 67 | Results). 68 | -------------------------------------------------------------------------------- /test/unnecessary_function_arguments_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(unnecessary_function_arguments_SUITE). 2 | 3 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 4 | -export([with_warnings/1, without_warnings/1, macros/1, ignore/1, ignore_config/1, 5 | ct_suite/1, export_all/1]). 6 | 7 | all() -> 8 | [with_warnings, without_warnings, macros, ignore, ct_suite, export_all]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "unnecessary_function_arguments"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc Hank finds unused function parameters 17 | with_warnings(_Config) -> 18 | ct:comment("Should detect and display warnings for unused function parameters"), 19 | 20 | FileA = "warnings_A.erl", 21 | FileB = "warnings_B.erl", 22 | FileC = "a_behaviour.erl", 23 | FileD = "a_behaviour_imp.erl", 24 | FileE = "gen_server_imp.erl", 25 | [#{file := FileC, 26 | text := <<"a_function_from_the_behaviour/1 doesn't need its #1 argument">>}, 27 | #{file := FileD, text := <<"non_exported_function_with/2 doesn't need its #1 argument">>}, 28 | #{file := FileE, text := <<"my_function/1 doesn't need its #1 argument">>}, 29 | #{file := FileE, text := <<"my_other_function/2 doesn't need its #2 argument">>}, 30 | #{file := FileA, text := <<"single_fun/2 doesn't need its #2 argument">>}, 31 | #{file := FileA, text := <<"multi_fun/3 doesn't need its #3 argument">>}, 32 | #{file := FileA, text := <<"unicode_αβåö/1 doesn't need its #1 argument"/utf8>>}, 33 | #{file := FileA, text := <<"with_nif_stub/2 doesn't need its #1 argument">>}, 34 | #{file := FileB, text := <<"underscore/3 doesn't need its #1 argument">>}] = 35 | analyze([FileA, FileB, FileC, FileD, FileE, "macro_behaviour_imp.erl"]), 36 | ok. 37 | 38 | %% @doc Hank finds nothing! 39 | without_warnings(_Config) -> 40 | ct:comment("Should not detect anything since the files are clean from warnings"), 41 | [] = 42 | analyze(["weird.erl", 43 | "clean.erl", 44 | "nifs.erl", 45 | "macro_behaviour_imp.erl", 46 | "parse_transf.erl"]), 47 | ok. 48 | 49 | %% @doc Macros as function names should work 50 | macros(_Config) -> 51 | ct:comment("Macros as function names should not crash hank"), 52 | [#{file := "macros.erl", text := <<"?MODULE/1 doesn't need its #1 argument">>}] = 53 | analyze(["macros.erl"]), 54 | ok. 55 | 56 | %% @doc Hank should correctly ignore warnings 57 | ignore(_Config) -> 58 | ct:comment("Should correctly ignore warnings"), 59 | [#{file := "ignore.erl", text := <<"ignore_arg2/2 doesn't need its #2 argument">>}] = 60 | analyze(["ignore.erl"]), 61 | ok. 62 | 63 | %% @doc No warnings since rebar.config specifically states that all of them 64 | %% should be ignored. 65 | ignore_config(_) -> 66 | File = "ignore_config.erl", 67 | Files = [File], 68 | IgnoreSpecs = 69 | [{File, 70 | unnecessary_function_arguments, 71 | [{ignore_arg2, 3, 2}, 72 | {ignore_arg2, 2, 1}, 73 | {ignore_arg2, 2, 2}, 74 | {ignore_whole_func3, 3}, 75 | ignore_whole_func]}], 76 | #{results := [], unused_ignores := []} = 77 | hank_test_utils:analyze_and_sort(Files, IgnoreSpecs, [unnecessary_function_arguments]). 78 | 79 | %% @doc Common Test suites should be ignored 80 | ct_suite(_Config) -> 81 | ct:comment("CT suites should not generate warnings"), 82 | Files = 83 | case code:which(ct_suite) of 84 | non_existing -> % OTP < 23.2 85 | ["old_ct_SUITE.erl"]; 86 | _ -> 87 | ["ct_SUITE.erl"] 88 | end, 89 | [] = analyze(Files). 90 | 91 | %% @doc Compiler flag "export_all" should be supported 92 | export_all(_Config) -> 93 | [] = analyze(["export_all1.erl"]), 94 | [] = analyze(["export_all2.erl"]). 95 | 96 | analyze(Files) -> 97 | #{results := Results, unused_ignores := []} = 98 | hank_test_utils:analyze_and_sort(Files, [unnecessary_function_arguments]), 99 | Results. 100 | -------------------------------------------------------------------------------- /test/unused_callbacks_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(unused_callbacks_SUITE). 2 | 3 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 4 | -export([with_warnings/1, with_macros/1, without_warnings/1, ignore/1, ignore_config/1]). 5 | 6 | all() -> 7 | [with_warnings, with_macros, without_warnings, ignore, ignore_config]. 8 | 9 | init_per_testcase(_, Config) -> 10 | hank_test_utils:init_per_testcase(Config, "unused_callbacks"). 11 | 12 | end_per_testcase(_, Config) -> 13 | hank_test_utils:end_per_testcase(Config). 14 | 15 | %% @doc Hank finds unused callbacks 16 | with_warnings(_Config) -> 17 | ct:comment("Should detect and display warnings for unused callbacks"), 18 | 19 | File = "warnings.erl", 20 | [#{file := File, 21 | text := <<"Callback unused_callback/1 is not used anywhere in the module">>}] = 22 | analyze([File]), 23 | {comment, ""}. 24 | 25 | %% @doc Hank finds unused callbacks with macros 26 | with_macros(_Config) -> 27 | ct:comment("Should detect and display warnings for unused callbacks with macros"), 28 | 29 | File = "macros.erl", 30 | [#{file := File, 31 | text := <<"Callback unused_callback/0 is not used anywhere in the module">>}] = 32 | analyze([File]), 33 | {comment, ""}. 34 | 35 | %% @doc Hank finds nothing! 36 | without_warnings(_Config) -> 37 | ct:comment("Should not detect anything since the file is clean from warnings"), 38 | [] = analyze(["clean.erl"]), 39 | {comment, ""}. 40 | 41 | %% @doc Hank ignores some callbacks 42 | ignore(_Config) -> 43 | ct:comment("Should only detect the callbacks that are not ignored"), 44 | [#{file := "ignore.erl", 45 | text := <<"Callback just_one/0 is not used anywhere in the module">>}, 46 | #{file := "ignore.erl", 47 | text := <<"Callback just_one/2 is not used anywhere in the module">>}] = 48 | analyze(["ignore.erl"]), 49 | {comment, ""}. 50 | 51 | %% @doc No warnings since rebar.config specifically states that all of them 52 | %% should be ignored. 53 | ignore_config(_) -> 54 | File = "ignore_config.erl", 55 | Files = [File], 56 | IgnoreSpecs = 57 | [{File, unused_callbacks, [all_arities, {just_one, 0}, {just_one, 1}, {just_one, 2}]}], 58 | #{results := [], unused_ignores := []} = 59 | hank_test_utils:analyze_and_sort(Files, IgnoreSpecs, [unused_callbacks]). 60 | 61 | analyze(Files) -> 62 | #{results := Results, unused_ignores := []} = 63 | hank_test_utils:analyze_and_sort(Files, [unused_callbacks]), 64 | Results. 65 | -------------------------------------------------------------------------------- /test/unused_hrls_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Tests for the unused_hrls rule 2 | -module(unused_hrls_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([unused/1, local_include/1, remote_include/1, local_include_lib/1, 6 | remote_include_lib/1, versioned_include_lib/1]). 7 | 8 | all() -> 9 | [unused, 10 | local_include, 11 | remote_include, 12 | local_include_lib, 13 | remote_include_lib, 14 | versioned_include_lib]. 15 | 16 | init_per_testcase(_, Config) -> 17 | hank_test_utils:init_per_testcase(Config, "unused_hrls"). 18 | 19 | end_per_testcase(_, Config) -> 20 | hank_test_utils:end_per_testcase(Config). 21 | 22 | %% @doc Hank finds unused header files 23 | unused(_) -> 24 | ct:comment("If there are no Erlang files, all hrls should be unused."), 25 | OnlyHrls = ["lib/app0-with-other-name/include/header.hrl", "lib/app1/include/header.hrl"], 26 | [#{file := "lib/app0-with-other-name/include/header.hrl", text := "This file is unused"}, 27 | #{file := "lib/app1/include/header.hrl", text := "This file is unused"}] = 28 | analyze(OnlyHrls), 29 | 30 | ct:comment("If there are Erlang files that don't include the hrls, all " 31 | "hrls should be unused."), 32 | WithErls = 33 | ["lib/app0-with-other-name/include/header.hrl", 34 | "lib/app1/include/header.hrl", 35 | "lib/app1/src/app1_not_using_header.erl", 36 | "lib/app2/src/app2_not_using_header.erl"], 37 | [#{file := "lib/app0-with-other-name/include/header.hrl"}, 38 | #{file := "lib/app1/include/header.hrl"}] = 39 | analyze(WithErls), 40 | 41 | {comment, ""}. 42 | 43 | %% @doc Hank detects that a header file is used with an include 44 | local_include(_) -> 45 | ct:comment("lib/app1/include/header.hrl should not be marked as unused " 46 | "since it is used locally"), 47 | Apps1And0 = 48 | ["lib/app1/include/header.hrl", 49 | "lib/app1/src/app1_not_using_header.erl", 50 | "lib/app1/src/app1_include.erl"], 51 | [] = analyze(Apps1And0), 52 | 53 | ct:comment("include/header.hrl should not be marked as unused since it " 54 | "is used locally"), 55 | ok = 56 | file:set_cwd( 57 | filename:join( 58 | code:lib_dir(rebar3_hank), "test/files/unused_hrls/lib/app1")), 59 | OnlyApp1 = 60 | ["include/header.hrl", "src/app1_not_using_header.erl", "src/app1_include.erl"], 61 | [] = analyze(OnlyApp1), 62 | 63 | {comment, ""}. 64 | 65 | remote_include(_) -> 66 | ct:comment("lib/app1/include/header.hrl should not be marked as unused " 67 | "since it is used from app2"), 68 | Apps1And0 = 69 | ["lib/app0-with-other-name/include/header.hrl", 70 | "lib/app1/include/header.hrl", 71 | "lib/app2/src/app2_not_using_header.erl", 72 | "lib/app2/src/app2_include.erl"], 73 | [#{file := "lib/app0-with-other-name/include/header.hrl", 74 | text := "This file is unused"}] = 75 | analyze(Apps1And0), 76 | 77 | {comment, ""}. 78 | 79 | local_include_lib(_) -> 80 | ct:comment("include/header.hrl should not be marked as unused since it " 81 | "is used in app1_include_lib"), 82 | Apps = 83 | #{app0 => "lib/app0-with-other-name", 84 | app1 => "lib/app1", 85 | app2 => "lib/app2"}, 86 | Context = hank_test_utils:mock_context(Apps, maps:keys(Apps)), 87 | hank_test_utils:set_cwd("unused_hrls/lib/app1"), 88 | OnlyApp1 = 89 | ["include/header.hrl", "src/app1_not_using_header.erl", "src/app1_include_lib.erl"], 90 | [] = analyze(OnlyApp1, Context), 91 | 92 | {comment, ""}. 93 | 94 | remote_include_lib(_) -> 95 | ct:comment("No header file should be marked as unused since they're both " 96 | "used in app2_include_lib"), 97 | Apps1And0 = 98 | ["lib/app0-with-other-name/include/header.hrl", 99 | "lib/app1/include/header.hrl", 100 | "lib/app2/src/app2_not_using_header.erl", 101 | "lib/app2/src/app2_include_lib.erl"], 102 | [] = analyze(Apps1And0), 103 | 104 | {comment, ""}. 105 | 106 | versioned_include_lib(_) -> 107 | ct:comment("No header file should be marked as unused since they're both " 108 | "used in app1_include_lib"), 109 | Apps1And0 = 110 | ["lib/app0-with-other-name/include/header.hrl", 111 | "lib/app1/include/header.hrl", 112 | "lib/app1/src/app1_not_using_header.erl", 113 | "lib/app1/src/app1_include_lib.erl"], 114 | [] = analyze(Apps1And0), 115 | 116 | {comment, ""}. 117 | 118 | analyze(Files) -> 119 | Apps = 120 | #{app0 => "lib/app0-with-other-name", 121 | app1 => "lib/app1", 122 | app2 => "lib/app2"}, 123 | Context = hank_test_utils:mock_context(Apps, maps:keys(Apps)), 124 | analyze(Files, Context). 125 | 126 | analyze(Files, Context) -> 127 | #{results := Results, unused_ignores := []} = 128 | hank_test_utils:analyze_and_sort(Files, [unused_hrls], Context), 129 | Results. 130 | -------------------------------------------------------------------------------- /test/unused_ignores_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Tests for the unused ignore warnings 2 | -module(unused_ignores_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([unused_ignores/1]). 6 | 7 | all() -> 8 | [unused_ignores]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "unused_ignores"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc Hank finds unused record files 17 | unused_ignores(_) -> 18 | Files = filelib:wildcard("*.?rl"), 19 | IgnoreSpecs = [{"ignore_config.erl", unused_macros, ["MACRO_FROM_CONFIG"]}], 20 | #{results := [], 21 | unused_ignores := 22 | [{"ignore_config.erl", unused_macros, ["MACRO_FROM_CONFIG"]}, 23 | {"unused_ignores.erl", bad_rule, all}, 24 | {"unused_ignores.erl", 25 | unnecessary_function_arguments, 26 | [{non_exported_function_with, 2, 1}]}, 27 | {"unused_ignores.erl", unused_macros, ["MACRO", "MICRO"]}]} = 28 | hank_test_utils:analyze_and_sort(Files, IgnoreSpecs, [unused_macros]). 29 | -------------------------------------------------------------------------------- /test/unused_macros_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Tests for the unused_macros rule 2 | -module(unused_macros_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([unused_macros/1]). 6 | 7 | all() -> 8 | [unused_macros]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "unused_macros"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc Hank finds unused record files 17 | unused_macros(_) -> 18 | Files = filelib:wildcard("*.?rl"), 19 | IgnoreSpecs = 20 | [{"ignore_config.erl", 21 | unused_macros, 22 | ["MACRO_ALL", {"MACRO_0", 0}, {"MACRO_1", 1}, {"MACRO_NONE", none}]}], 23 | #{results := 24 | [#{file := "double.erl", text := <<"?Y is unused">>}, 25 | #{file := "double.erl", text := <<"?Y is unused">>}, 26 | #{file := "header.hrl", text := <<"?UNUSED_MACRO is unused">>}, 27 | #{file := "header.hrl", text := <<"?UNUSED_MACRO_WITH/0 is unused">>}, 28 | #{file := "header.hrl", text := <<"?UNUSED_MACRO_WITH/1 is unused">>}, 29 | #{file := "header.hrl", text := <<"?UNUSED_MACRO_WITH/2 is unused">>}, 30 | #{file := "header.hrl", text := <<"?UNUSED_MACRO_UNICODE_ÇØÍ is unused"/utf8>>}, 31 | #{file := "ignore.erl", text := <<"?MACRO_0 is unused">>}, 32 | #{file := "ignore.erl", text := <<"?MACRO_0/1 is unused">>}, 33 | #{file := "ignore.erl", text := <<"?MACRO_0/2 is unused">>}, 34 | #{file := "ignore.erl", text := <<"?MACRO_1 is unused">>}, 35 | #{file := "ignore.erl", text := <<"?MACRO_1/0 is unused">>}, 36 | #{file := "ignore.erl", text := <<"?MACRO_1/2 is unused">>}, 37 | #{file := "ignore.erl", text := <<"?MACRO_NONE/0 is unused">>}, 38 | #{file := "ignore.erl", text := <<"?MACRO_NONE/1 is unused">>}, 39 | #{file := "ignore.erl", text := <<"?MACRO_NONE/2 is unused">>}, 40 | #{file := "unused_macro_sample.erl", text := <<"?UNUSED_MACRO is unused">>}, 41 | #{file := "unused_macro_sample.erl", text := <<"?UNUSED_MACRO_WITH/0 is unused">>}, 42 | #{file := "unused_macro_sample.erl", text := <<"?UNUSED_MACRO_WITH/1 is unused">>}, 43 | #{file := "unused_macro_sample.erl", text := <<"?UNUSED_MACRO_WITH/2 is unused">>}, 44 | #{file := "unused_macro_sample.erl", 45 | text := <<"?UNUSED_MACRO_UNICODE_ÇØÍ is unused"/utf8>>}, 46 | #{file := "unused_macro_sample.erl", text := <<"?macroIsAnAtom is unused">>}], 47 | unused_ignores := []} = 48 | hank_test_utils:analyze_and_sort(Files, IgnoreSpecs, [unused_macros]). 49 | -------------------------------------------------------------------------------- /test/unused_record_fields_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Tests for the unused_record_fields rule 2 | -module(unused_record_fields_SUITE). 3 | 4 | -export([all/0, init_per_testcase/2, end_per_testcase/2]). 5 | -export([unused_record_fields/1]). 6 | 7 | all() -> 8 | [unused_record_fields]. 9 | 10 | init_per_testcase(_, Config) -> 11 | hank_test_utils:init_per_testcase(Config, "unused_record_fields"). 12 | 13 | end_per_testcase(_, Config) -> 14 | hank_test_utils:end_per_testcase(Config). 15 | 16 | %% @doc Hank finds unused record files 17 | unused_record_fields(_) -> 18 | Files = filelib:wildcard("*.?rl"), 19 | IgnoreSpecs = 20 | [{"ignore_config.erl", 21 | unused_record_fields, 22 | [ignored_record, {a_record, ignored_field1}, {a_record, ignored_field2}]}], 23 | #{results := 24 | [#{file := "header.hrl", 25 | text := <<"Field really_unused_field in record really_unused_record is unused">>}, 26 | #{file := "ignore.erl", text := <<"Field unused_field in record a_record is unused">>}, 27 | #{file := "macros.erl", text := <<"Field unused_field in record a_rec is unused">>}, 28 | #{file := "unused_record_field_sample.erl", 29 | text := <<"Field unused_field in record a_record is unused">>}, 30 | #{file := "unused_record_field_sample.erl", 31 | text := <<"Field unused_typed_field in record a_record is unused">>}, 32 | #{file := "unused_record_field_sample.erl", 33 | text := <<"Field unused_field_with_default in record a_record is unused">>}, 34 | #{file := "unused_record_field_sample.erl", 35 | text := 36 | <<"Field unused_typed_field_with_default in record a_record is " 37 | "unused">>}, 38 | #{file := "unused_record_field_sample.erl", 39 | text := <<"Field 'attr_αåβö' in record 'unicode_αåβö' is unused"/utf8>>}], 40 | unused_ignores := []} = 41 | hank_test_utils:analyze_and_sort(Files, IgnoreSpecs, [unused_record_fields]). 42 | --------------------------------------------------------------------------------