Packages

A QuickCheck-inspired property-based testing tool for Erlang

Current section

Files

Jump to
proper src proper_unused_imports_remover.erl
Raw

src/proper_unused_imports_remover.erl

%%% -*- coding: utf-8 -*-
%%% -*- erlang-indent-level: 2 -*-
%%% -------------------------------------------------------------------
%%% Copyright 2015-2019 Manolis Papadakis <manopapad@gmail.com>,
%%% Eirini Arvaniti <eirinibob@gmail.com>
%%% and Kostis Sagonas <kostis@cs.ntua.gr>
%%%
%%% This file is part of PropEr.
%%%
%%% PropEr is free software: you can redistribute it and/or modify
%%% it under the terms of the GNU General Public License as published by
%%% the Free Software Foundation, either version 3 of the License, or
%%% (at your option) any later version.
%%%
%%% PropEr is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%%% GNU General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
%%% @copyright 2015-2019 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
%%% @version {@version}
%%% @author Zaiming Shi (modifications and update by Kostis Sagonas)
-module(proper_unused_imports_remover).
-export([parse_transform/2]).
-include("proper_internal.hrl").
-type key() :: {fun_name(), arity()}.
-type val() :: {erl_anno:line(), mod_name(), boolean()}.
-type imp_dict() :: dict:dict(key(), val()).
-define(IMP_MODULES,
[proper, proper_statem, proper_symb, proper_types, proper_unicode]).
-spec parse_transform([abs_form()], [compile:option()]) -> [abs_form()].
parse_transform(Forms, Options) ->
case lists:member(warn_unused_import, Options) of
true -> parse(Forms, [], []);
false -> Forms
end.
-spec parse([abs_form()], [abs_form()], [abs_form()]) -> [abs_form()].
parse([{attribute, _L, import, {?MODULE, []}} | Rest], Imports, Acc) ->
lists:reverse(Acc) ++ use_new_imports(to_dict(Imports), Rest);
parse([{attribute, _L, import, {Mod, _Funs}} = A | Rest], Imports, Acc) ->
case lists:member(Mod, ?IMP_MODULES) of
true -> parse(Rest, [A | Imports], Acc);
false -> parse(Rest, Imports, [A | Acc])
end;
parse([Form | Rest], Imports, Acc) ->
parse(Rest, Imports, [Form | Acc]).
-spec use_new_imports(imp_dict(), [abs_form()]) -> [abs_form()].
use_new_imports(Dict0, Forms) ->
Dict = mark_used_imports(Dict0, Forms),
new_import_attributes(Dict) ++ Forms.
-spec mark_used_imports(imp_dict(), [abs_form()]) -> imp_dict().
mark_used_imports(Dict, Forms) ->
lists:foldl(fun scan_forms/2, Dict, Forms).
-spec scan_forms(abs_form(), imp_dict()) -> imp_dict().
scan_forms({function, _L, _F, _A, ClauseSeq}, Dict) ->
lists:foldl(fun brutal_scan/2, Dict, ClauseSeq);
scan_forms(_, Dict) ->
Dict.
-spec brutal_scan(abs_expr() | [abs_clause()], imp_dict()) -> imp_dict().
brutal_scan({'fun', _L, {function, Name, Arity}}, Dict) ->
maybe_update_dict({Name, Arity}, Dict);
brutal_scan({call, _L1, Call, Args}, Dict0) ->
case Call of
{atom, _L2, Name} ->
Dict = maybe_update_dict({Name, length(Args)}, Dict0),
brutal_scan(Args, Dict);
_ ->
brutal_scan([Call | Args], Dict0)
end;
brutal_scan(Other, Dict) when is_list(Other) ->
lists:foldl(fun brutal_scan/2, Dict, Other);
brutal_scan(Other, Dict) when is_tuple(Other) ->
brutal_scan(tuple_to_list(Other), Dict);
brutal_scan(_Other, Dict) ->
Dict.
-spec maybe_update_dict(key(), imp_dict()) -> imp_dict().
maybe_update_dict(Key, Dict) ->
case dict:find(Key, Dict) of
{ok, {Line, Mod, false}} ->
dict:store(Key, {Line, Mod, true}, Dict);
_Other ->
Dict
end.
-spec to_dict([abs_form()]) -> imp_dict().
to_dict(Imports) -> to_dict(Imports, dict:new()).
-spec to_dict([abs_form()], imp_dict()) -> imp_dict().
to_dict([], Dict) ->
Dict;
to_dict([{attribute, Line, import, {Mod, FALst}} | Rest], Dict0) ->
to_dict(Rest, lists:foldl(fun(FA, Dict) ->
dict:store(FA, {Line, Mod, false}, Dict)
end, Dict0, FALst)).
-spec new_import_attributes(imp_dict()) -> [abs_form()].
new_import_attributes(Dict) ->
LMFAs = [{Line, Mod, FA} || {FA, {Line, Mod, true}} <- dict:to_list(Dict)],
Imports = lists:keysort(1, LMFAs),
lists:reverse(lists:foldl(fun add_new_attribute/2, [], Imports)).
-type lmfa() :: {erl_anno:line(), mod_name(), key()}.
-spec add_new_attribute(lmfa(), [abs_form()]) -> [abs_form()].
add_new_attribute({Line, Mod, FA}, [{_, Line, _, {Mod, FALst}} | Attributes]) ->
[{attribute, Line, import, {Mod, [FA | FALst]}} | Attributes];
add_new_attribute({Line, Mod, FA}, Attributes) ->
[{attribute, Line, import, {Mod, [FA]}} | Attributes].