Merge remote-tracking branch 'canonical/next'
[sinan.git] / src / sin_task_xref.erl
blobeaad0a55fd05921f1e653b3a8d9fc3b8d3e9e90b
1 %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
2 %%%---------------------------------------------------------------------------
3 %%% @author Eric Merritt <ericbmerritt@gmail.com>
4 %%% @doc
5 %%% Runs the 'test' function on all modules in an application
6 %%% if that function exits.
7 %%% @end
8 %%% @copyright (C) 2011 Erlware
9 %%%---------------------------------------------------------------------------
10 -module(sin_task_xref).
12 -behaviour(sin_task).
14 -export([description/0, do_task/2]).
16 -include_lib("sinan/include/sinan.hrl").
18 -define(TASK, xref).
19 -define(DEPS, [build]).
21 %%====================================================================
22 %% API
23 %%====================================================================
24 %% @doc provide a description for this task
25 -spec description() -> sin_task:task_description().
26 description() ->
28 Desc = "
29 xref Task
30 =========
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.",
40 #task{name = ?TASK,
41 task_impl = ?MODULE,
42 bare = false,
43 deps = ?DEPS,
44 desc = Desc,
45 short_desc = "Runs xref on the project, to detect problems",
46 example = "xref",
47 opts = []}.
49 %% @doc do the xref task
50 -spec do_task(sin_config:config(), sin_state:state()) ->
51 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),
59 ModuleInfo =
60 lists:flatten(lists:map(fun(App=#app{modules=Modules}) ->
61 xref_app(Config, State, ServerName, App),
62 Modules
63 end,
64 Apps)),
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))
70 end,
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),
77 State.
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()) ->
82 ok | fail.
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
90 {ok, _AppNameVsn} ->
91 ok;
92 Error = {error, Module, Reason} ->
93 sin_log:normal(Config, Module:format_error(Reason)),
94 ?SIN_RAISE(State, {Error, Module:format_error(Reason)})
95 end.
97 %%====================================================================
98 %%% Internal functions
99 %%====================================================================
101 %% @doc print out the appropriate message for the responce
102 -spec notify_user(sin_config:config(),
103 sin_state:state(),
104 [term()], atom(), {error, atom(), term()} |
105 {ok, [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)
113 end, AnswerList).
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
120 false ->
121 sin_log:normal(Config, "~s is exported but not used", [format_mfa(MFA)]);
122 true ->
124 end;
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),
130 format_mfa(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),
135 format_mfa(Caller),
136 format_mfa(Callee)]).
138 %% @doc return a name that is probably unique for the xref server.
139 -spec get_a_uniquish_name() ->
140 atom().
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()}) ->
146 string().
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
151 %% exporting it
152 -spec is_eunit_test({atom(), atom(), integer()}) ->
153 boolean().
154 is_eunit_test({M, F, _A}) ->
155 HasTest = case lists:keyfind(exports, 1, M:module_info()) of
156 {exports, List} ->
157 lists:member({test, 0}, List);
158 _ ->
159 false
160 end,
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()) ->
165 boolean().
166 check_test(F, T) ->
167 FunStr = atom_to_list(F),
168 Len = erlang:length(FunStr),
169 Index = string:str(FunStr, T),
170 Len - Index == 0.
172 %% @doc find the module file in the list of modules that was built
173 -spec find_module([term()], {Module::atom(), atom(), atom()}) ->
174 File::string().
175 find_module(ModuleInfo, {M, _, _}) ->
176 case lists:keyfind(M, 2, ModuleInfo) of
177 {File, M, _} ->
178 File;
179 _ ->
181 end.