Skip to content

Commit 2c83144

Browse files
committed
Merge pull request #1 from inaka/elbrujohalcon.1.the_module
The Module
2 parents a98e12e + 4e0c5de commit 2c83144

10 files changed

+514
-3
lines changed

Makefile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ ERLC_OPTS += +warn_export_vars +warn_exported_vars +warn_missing_spec +warn_unty
1717

1818
include erlang.mk
1919

20+
# To avoid eunit autocompile
21+
TEST_ERLC_OPTS = +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
22+
TEST_ERLC_OPTS += +'{parse_transform, lager_transform}'
23+
2024
# Commont Test Config
2125

22-
TEST_ERLC_OPTS += +'{parse_transform, lager_transform}'
23-
CT_SUITES = xref_runner
24-
CT_OPTS = -cover test/xref_runner.coverspec
26+
CT_OPTS += -cover test/xref_runner.coverspec -vvv
2527

2628
SHELL_OPTS= -name ${PROJECT}@`hostname` -s sync
29+
30+
test-shell: build-ct-suites app
31+
erl -pa ebin -pa deps/*/ebin -pa test -s sync -s lager

src/xref_runner.erl

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
%% -------------------------------------------------------------------
2+
%% This module is basically copied from rebar's rebar_xref, which in turn
3+
%% borrows heavily from rebar's http://github.com/etnt/eCheck project as
4+
%% written by Torbjorn Tornkvist <[email protected]>, Daniel Luna
5+
%% <[email protected]> and others.
6+
%% -------------------------------------------------------------------
7+
-module(xref_runner).
8+
-author('[email protected]').
9+
10+
-type check() :: undefined_function_calls
11+
| undefined_functions
12+
| locals_not_used
13+
| exports_not_used
14+
| deprecated_function_calls
15+
| deprecated_functions.
16+
17+
-type xref_default() :: {builtins | recurse | verbose | warnings, boolean()}.
18+
19+
-type config() :: #{ extra_paths => [file:name_all()]
20+
, xref_defaults => [xref_default()]
21+
, dirs => [file:name_all()]
22+
}.
23+
-type warning() :: #{ filename => file:name_all()
24+
, line => non_neg_integer()
25+
, source => mfa()
26+
, target => mfa()
27+
}.
28+
29+
-export_type([check/0, xref_default/0, config/0, warning/0]).
30+
-export([check/2]).
31+
32+
-spec check(check(), config()) -> [warning()].
33+
check(Check, Config) ->
34+
XrefDefaults = maps:get(xref_defaults, Config, []),
35+
Dirs = maps:get(dirs, Config, ["ebin"]),
36+
37+
{ok, Xref} = xref:start(?MODULE),
38+
try
39+
ok = xref:set_library_path(Xref, code_path(Config)),
40+
41+
xref:set_default(Xref, XrefDefaults),
42+
43+
lists:foreach(
44+
fun(Dir) ->
45+
{ok, _} = xref:add_directory(Xref, Dir)
46+
end, Dirs),
47+
48+
{ok, Results} = xref:analyze(Xref, Check),
49+
50+
FilteredResults = filter_xref_results(Check, Results),
51+
52+
[result_to_warning(Result) || Result <- FilteredResults]
53+
after
54+
stopped = xref:stop(Xref)
55+
end.
56+
57+
%% ===================================================================
58+
%% Internal functions
59+
%% ===================================================================
60+
61+
code_path(Config) ->
62+
ExtraPaths = maps:get(extra_paths, Config, []),
63+
[P || P <- code:get_path() ++ ExtraPaths, filelib:is_dir(P)].
64+
65+
filter_xref_results(Check, Results) ->
66+
SourceModules =
67+
lists:usort([source_module(Result) || Result <- Results]),
68+
69+
Ignores =
70+
lists:flatmap(
71+
fun(Module) -> get_ignorelist(Module, Check) end, SourceModules),
72+
73+
[Result || Result <- Results,
74+
not lists:member(parse_xref_result(Result), Ignores)].
75+
76+
source_module({Mt, _Ft, _At}) -> Mt;
77+
source_module({{Ms, _Fs, _As}, _Target}) -> Ms.
78+
79+
%%
80+
%% Ignore behaviour functions, and explicitly marked functions
81+
%%
82+
%% Functions can be ignored by using
83+
%% -ignore_xref([{F, A}, {M, F, A}...]).
84+
get_ignorelist(Mod, Check) ->
85+
%% Get ignore_xref attribute and combine them in one list
86+
Attributes =
87+
try
88+
Mod:module_info(attributes)
89+
catch
90+
_Class:_Error -> []
91+
end,
92+
93+
IgnoreXref =
94+
[mfa(Mod, Value) || {ignore_xref, Values} <- Attributes, Value <- Values],
95+
96+
BehaviourCallbacks = get_behaviour_callbacks(Check, Mod, Attributes),
97+
98+
%% And create a flat {M,F,A} list
99+
IgnoreXref ++ BehaviourCallbacks.
100+
101+
get_behaviour_callbacks(exports_not_used, Mod, Attributes) ->
102+
Behaviours = [Value || {behaviour, Values} <- Attributes, Value <- Values],
103+
[{Mod, {Mod, F, A}}
104+
|| B <- Behaviours, {F, A} <- B:behaviour_info(callbacks)];
105+
get_behaviour_callbacks(_Check, _Mod, _Attributes) ->
106+
[].
107+
108+
mfa(M, {F, A}) -> {M, {M, F, A}};
109+
mfa(M, MFA) -> {M, MFA}.
110+
111+
parse_xref_result({{SM, _, _}, MFAt}) -> {SM, MFAt};
112+
parse_xref_result({TM, _, _} = MFAt) -> {TM, MFAt}.
113+
114+
result_to_warning({MFASource, MFATarget}) ->
115+
{Filename, Line} = get_source(MFASource),
116+
#{ filename => Filename
117+
, line => Line
118+
, source => MFASource
119+
, target => MFATarget
120+
};
121+
result_to_warning(MFA) ->
122+
{Filename, Line} = get_source(MFA),
123+
#{ filename => Filename
124+
, line => Line
125+
, source => MFA
126+
}.
127+
128+
%%
129+
%% Given a MFA, find the file and LOC where it's defined. Note that
130+
%% xref doesn't work if there is no abstract_code, so we can avoid
131+
%% being too paranoid here.
132+
%%
133+
get_source({M, F, A}) ->
134+
case code:get_object_code(M) of
135+
error -> {"", 0};
136+
{M, Bin, _} -> find_function_source(M, F, A, Bin)
137+
end.
138+
139+
find_function_source(M, F, A, Bin) ->
140+
AbstractCode = beam_lib:chunks(Bin, [abstract_code]),
141+
{ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode,
142+
143+
%% Extract the original source filename from the abstract code
144+
[{attribute, 1, file, {Source, _}} | _] = Code,
145+
146+
%% Extract the line number for a given function def
147+
Fn = [E || E <- Code,
148+
element(1, E) == function,
149+
element(3, E) == F,
150+
element(4, E) == A],
151+
152+
case Fn of
153+
[{function, Line, F, _, _}] -> {Source, Line};
154+
%% do not crash if functions are exported, even though they
155+
%% are not in the source.
156+
%% parameterized modules add new/1 and instance/1 for example.
157+
[] -> {Source, 0}
158+
end.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-module(deprecated_function_calls).
2+
3+
-deprecated({internal, '_'}).
4+
5+
-export([bad/0, bad/1, good/0, internal/0]).
6+
7+
bad() ->
8+
deprecated_functions:deprecated().
9+
10+
bad(1) ->
11+
deprecated_function_calls:internal(),
12+
deprecated_functions:deprecated(1).
13+
14+
good() ->
15+
deprecated_functions:not_deprecated().
16+
17+
internal() -> {this, is, deprecated}.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-module(deprecated_functions).
2+
3+
-deprecated({deprecated, 0}).
4+
-deprecated({deprecated, '_', eventually}).
5+
6+
-export([deprecated/0, deprecated/1, not_deprecated/0]).
7+
8+
deprecated() -> {this, function, is, deprecated}.
9+
10+
deprecated(SomeDay) -> {this, function, will, be, removed, SomeDay}.
11+
12+
not_deprecated() -> {this, function, isnt, deprecated}.

test/examples/exports_not_used.erl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-module(exports_not_used).
2+
3+
-export([exported/0, export_not/1]).
4+
5+
exported() -> {it, is, exported}.
6+
7+
exported_and_locally(Used) -> {it, is, exported, 'and', locally, Used}.
8+
9+
export_not(Used) -> local(Used), {it, is, not Used}.
10+
11+
local(Used) -> exported_and_locally(Used).

test/examples/ignore_xref.erl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-module(ignore_xref).
2+
3+
-deprecated({internal, '_'}).
4+
-ignore_xref({deprecated_functions, deprecated, 1}).
5+
-ignore_xref({ignored, 0}).
6+
7+
-export([bad/0, bad/1, good/0, internal/0, ignored/0]).
8+
9+
bad() ->
10+
deprecated_functions:deprecated().
11+
12+
bad(1) ->
13+
ignore_xref:internal(),
14+
deprecated_functions:deprecated(1).
15+
16+
good() ->
17+
deprecated_functions:not_deprecated().
18+
19+
internal() -> {this, is, deprecated}.
20+
21+
ignored() ->
22+
ignore_xref:ignored().

test/examples/locals_not_used.erl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-module(locals_not_used).
2+
3+
-export([exported/0]).
4+
5+
exported() -> local(true), {it, is, exports_not_used:exported()}.
6+
7+
local(Used) -> lists:foreach(fun inline/1, [true]), {it, is, Used}.
8+
9+
local_not(Used) -> {it, is, not Used}.
10+
11+
inline(Used) -> {it, is, Used, inline}.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-module(undefined_function_calls).
2+
3+
-export([bad/0, bad/1]).
4+
5+
bad() ->
6+
undefined_function_calls:undefined_here(),
7+
undefined_function_calls:bad(1).
8+
9+
bad(1) ->
10+
undefined_functions:undefined_there(),
11+
bad(2);
12+
bad(2) ->
13+
lists:foreach(
14+
fun other_module:undefined_somewhere_else/1, lists:seq(1, 10)).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-module(undefined_functions).
2+
3+
-export([bad/0, bad/1]).
4+
5+
bad() ->
6+
undefined_functions:undefined_here(),
7+
undefined_functions:bad(1).
8+
9+
bad(1) ->
10+
other_module:undefined_somewhere_else(),
11+
bad(2);
12+
bad(2) ->
13+
lists:foreach(
14+
fun other_module:undefined_somewhere_else/1, lists:seq(1, 10)).

0 commit comments

Comments
 (0)