1 %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
2 %%%---------------------------------------------------------------------------
3 %%% @author Eric Merritt <ericbmerritt@gmail.com>
5 %%% Runs the 'test' function on all modules in an application
6 %%% if that function exits.
8 %%% @copyright (C) 2011 Erlware
9 %%%---------------------------------------------------------------------------
10 -module(sin_task_xref
).
14 -export([description
/0, do_task
/2]).
16 -include_lib("sinan/include/sinan.hrl").
19 -define(DEPS
, [build
]).
21 %%====================================================================
23 %%====================================================================
24 %% @doc provide a description for this task
25 -spec
description() -> sin_task:task_description().
32 This command the built in erlang xref checker on all code in the
33 project. It outputs several different sections corresponding to the
34 information available from xref. xref does have the problem that if
35 dependent code is not compiled with debug_info it will display any calls
36 to that code as unresolved. This is unfortunate much of the code
37 in the core library is not compiled with debug info. So you will get
38 false positive reports for this code.",
45 short_desc
= "Runs xref on the project, to detect problems",
49 %% @doc do the xref task
50 -spec
do_task(sin_config:config(), sin_state:state()) ->
52 do_task(Config
, State
) ->
53 ServerName
= get_a_uniquish_name(),
54 ExistingPaths
= code:get_path(),
55 xref:start(ServerName
),
57 Apps
= sin_state:get_value(project_apps
, State
),
60 lists:flatten(lists:map(fun(App
=#app
{modules
=Modules
}) ->
61 xref_app(Config
, State
, ServerName
, App
),
66 lists:foreach(fun({Analysis
, Name
}) ->
67 sin_log:normal(Config
, "Looking for ~s", [Name
]),
68 notify_user(Config
, State
, ModuleInfo
, Analysis
,
69 xref:analyze(ServerName
, Analysis
))
71 [{undefined_function_calls
, "Undefined Function Calls"},
72 {locals_not_used
, "Unused Local Functions"},
73 {exports_not_used
, "Unused Exported Functions"},
74 {deprecated_function_calls
, "Calls to Deprecated Functions"}]),
75 code:set_path(ExistingPaths
),
76 xref:stop(ServerName
),
79 %% @doc add the application to the specified xref system
80 -spec
xref_app(sin_config:config(), sin_state:state(),
81 ServerName::atom(), AppName::atom()) ->
83 xref_app(Config
, State
, ServerName
, #app
{path
=AppDir
,properties
=Props
}) ->
84 Paths
= proplists:get_value(code_paths
, Props
),
86 xref:set_library_path(ServerName
, Paths
),
88 case xref:add_application(ServerName
, AppDir
,
89 [{warnings
, true
}]) of
92 Error
= {error
, Module
, Reason
} ->
93 sin_log:normal(Config
, Module:format_error(Reason
)),
94 ?
SIN_RAISE(State
, {Error
, Module:format_error(Reason
)})
97 %%====================================================================
98 %%% Internal functions
99 %%====================================================================
101 %% @doc print out the appropriate message for the responce
102 -spec
notify_user(sin_config:config(),
104 [term()], atom(), {error
, atom(), term()} |
107 notify_user(Config
, State
, _
, _
, Error
= {error
, Module
, Reason
}) ->
108 sin_log:normal(Config
, Module:format_error(Reason
)),
109 ?
SIN_RAISE(State
, {Error
, Module:format_error(Reason
)});
110 notify_user(Config
, _State
, ModuleInfo
, Analysis
, {ok
, AnswerList
}) ->
111 lists:foreach(fun(Answer
) ->
112 display_answer(Config
, Analysis
, ModuleInfo
, Answer
)
115 %% @doc print out an answer from the xref system
116 -spec
display_answer(sin_config:config(), atom(), [term()], term()) ->
118 display_answer(Config
, exports_not_used
, _
, MFA
) ->
119 case is_eunit_test(MFA
) of
121 sin_log:normal(Config
, "~s is exported but not used", [format_mfa(MFA
)]);
125 display_answer(Config
, locals_not_used
, _
, MFA
) ->
126 sin_log:normal(Config
, "~s is defined but not used", [format_mfa(MFA
)]);
127 display_answer(Config
, undefined_function_calls
, ModuleInfo
, {Caller
, Callee
}) ->
128 sin_log:normal(Config
, "~s:~s calls the undefined function ~s ",
129 [find_module(ModuleInfo
, Caller
),
131 format_mfa(Callee
)]);
132 display_answer(Config
, deprecated_function_calls
, ModuleInfo
, {Caller
, Callee
}) ->
133 sin_log:normal(Config
, "~s:~s calls the deprecated function ~s ",
134 [find_module(ModuleInfo
, Caller
),
136 format_mfa(Callee
)]).
138 %% @doc return a name that is probably unique for the xref server.
139 -spec
get_a_uniquish_name() ->
141 get_a_uniquish_name() ->
142 erlang:list_to_atom(erlang:integer_to_list(erlang:phash2({node(), now()}))).
144 %% @doc format the module, function, arity in a way that is readable to a human
145 -spec
format_mfa({atom(), atom(), integer()}) ->
147 format_mfa({Module
, Function
, Arity
}) ->
148 io_lib:format("~p:~p/~p", [Module
, Function
, Arity
]).
150 %% @doc check to see if the M,F, A is a test function, if so don't worry about
152 -spec
is_eunit_test({atom(), atom(), integer()}) ->
154 is_eunit_test({M
, F
, _A
}) ->
155 HasTest
= case lists:keyfind(exports
, 1, M:module_info()) of
157 lists:member({test
, 0}, List
);
161 HasTest orelse
check_test(F
, "_test") orelse
check_test(F
, "_test_").
163 %% @doc check if this is a test funciotn
164 -spec
check_test(atom(), string()) ->
167 FunStr
= atom_to_list(F
),
168 Len
= erlang:length(FunStr
),
169 Index
= string:str(FunStr
, T
),
172 %% @doc find the module file in the list of modules that was built
173 -spec
find_module([term()], {Module::atom(), atom(), atom()}) ->
175 find_module(ModuleInfo
, {M
, _
, _
}) ->
176 case lists:keyfind(M
, 2, ModuleInfo
) of