Packages
elvis_core
1.3.1
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
retired
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.0
3.0.1
3.0.0
2.0.1
2.0.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0
0.7.0
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
Core library for the Erlang style reviewer
Current section
Files
Jump to
Current section
Files
src/elvis_core.erl
-module(elvis_core).
%% Public API
-export([ rock/1
, rock_this/2
]).
-export([start/0]).
%% for internal use only
-export([do_rock/2]).
%% for eating our own dogfood
-export([main/1]).
-ifdef(TEST).
-export([apply_rule/2]).
-endif.
-type source_filename() :: nonempty_string().
-type target() :: source_filename() | module().
-type rule_config() :: #{ atom() => term() }.
-export_type([rule_config/0]).
-type rule() :: {Module :: module(), Function :: atom(), RuleConfig :: rule_config()}
| {Module :: module(), Function :: atom()}.
-export_type([rule/0]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% @doc Used when starting the application on the shell.
-spec start() -> ok.
start() ->
{ok, _} = application:ensure_all_started(elvis_core),
ok.
%%% Rock Command
-spec rock(elvis_config:configs()) -> ok | {fail, [elvis_result:file()]}.
rock(Config) ->
ok = elvis_config:validate(Config),
NewConfig = elvis_config:normalize(Config),
Results = lists:map(fun do_parallel_rock/1, NewConfig),
lists:foldl(fun combine_results/2, ok, Results).
-spec rock_this(target(), elvis_config:configs()) ->
ok | {fail, [elvis_result:file() | elvis_result:rule()]}.
rock_this(Module, Config) when is_atom(Module) ->
ModuleInfo = Module:module_info(compile),
Path = proplists:get_value(source, ModuleInfo),
rock_this(Path, Config);
rock_this(Path, Config) ->
elvis_config:validate(Config),
NewConfig = elvis_config:normalize(Config),
Dirname = filename:dirname(Path),
Filename = filename:basename(Path),
File = case elvis_file:find_files([Dirname], Filename) of
[] -> throw({enoent, Path});
[File0] -> File0
end,
FilterFun = fun(Cfg) ->
Filter = elvis_config:filter(Cfg),
Dirs = elvis_config:dirs(Cfg),
IgnoreList = elvis_config:ignore(Cfg),
[] =/= elvis_file:filter_files([File], Dirs, Filter,
IgnoreList)
end,
case lists:filter(FilterFun, NewConfig) of
[] ->
elvis_utils:info("Skipping ~s", [Path]);
FilteredConfig ->
LoadedFile = load_file_data(FilteredConfig, File),
ApplyRulesFun = fun(Cfg) -> apply_rules_and_print(Cfg, LoadedFile) end,
Results = lists:map(ApplyRulesFun, FilteredConfig),
elvis_result_status(Results)
end.
%% @private
-spec do_parallel_rock(elvis_config:config())
-> ok | {fail, [elvis_result:file() | elvis_result:rule()]}.
do_parallel_rock(Config0) ->
Parallel = elvis_config:from_application_or_config(parallel, 1),
Config = elvis_config:resolve_files(Config0),
Files = elvis_config:files(Config),
{ok, Results} =
elvis_task:chunk_fold({?MODULE, do_rock},
fun(Elem, Acc) ->
elvis_result:print_results(Elem),
{ok, [Elem | Acc]}
end,
[], [Config], Files, Parallel),
elvis_result_status(Results).
-spec do_rock(elvis_file:file(), elvis_config:configs() | elvis_config:config())
-> {ok, elvis_result:file()}.
do_rock(File, Config) ->
LoadedFile = load_file_data(Config, File),
Results = apply_rules(Config, LoadedFile),
{ok, Results}.
%% @private
-spec load_file_data(elvis_config:configs() | elvis_config:config(), elvis_file:file())
-> elvis_file:file().
load_file_data(Config, File) ->
Path = elvis_file:path(File),
elvis_utils:info("Loading ~s", [Path]),
try
elvis_file:load_file_data(Config, File)
catch
_:Reason ->
Msg = "~p when loading file ~p.",
elvis_utils:error_prn(Msg, [Reason, Path]),
File
end.
%% @private
-spec main([]) -> true | no_return().
main([]) ->
ok = application:load(elvis_core),
R = rock(elvis_config:from_file("elvis.config")),
R =:= ok orelse halt(1).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec combine_results(ok | {fail, [elvis_result:file()]},
ok | {fail, [elvis_result:file()]}) ->
ok | {fail, [elvis_result:file()]}.
combine_results(ok, Acc) ->
Acc;
combine_results(Item, ok) ->
Item;
combine_results({fail, ItemResults}, {fail, AccResults}) ->
{fail, ItemResults ++ AccResults}.
apply_rules_and_print(Config, File) ->
Results = apply_rules(Config, File),
elvis_result:print_results(Results),
Results.
-spec apply_rules(elvis_config:configs() | elvis_config:config(), File::elvis_file:file()) ->
elvis_result:file().
apply_rules(Config, File) ->
Rules = elvis_config:rules(Config),
Acc = {[], Config, File},
{ParseTree, _} = elvis_file:parse_tree(Config, File),
{RulesResults, _, _} = lists:foldl(fun apply_rule/2, Acc,
merge_rules({file, ParseTree}, lists:flatten(Rules))),
elvis_result:new(file, File, RulesResults).
merge_rules({file, ParseTree}, ElvisConfigRules) ->
ElvisAttrs = elvis_code:find(fun is_elvis_attr/1, ParseTree,
#{ traverse => content, mode => node }),
ElvisAttrRules = elvis_attr_rules(ElvisAttrs),
elvis_config:merge_rules(ElvisAttrRules, ElvisConfigRules).
is_elvis_attr(Node) ->
ktn_code:type(Node) =:= elvis.
elvis_attr_rules([] = _ElvisAttrs) ->
[];
elvis_attr_rules(ElvisAttrs) ->
[Rule || ElvisAttr <- ElvisAttrs, Rule <- ktn_code:attr(value, ElvisAttr)].
-spec apply_rule({Mod, Fun} | {Mod, Fun, RuleCfg}, {Results, ElvisCfg, File}) -> Result
when Mod :: module(),
Fun :: atom(),
RuleCfg :: rule_config(),
Results :: [elvis_result:rule()],
ElvisCfg :: elvis_config:config(),
File :: elvis_file:file(),
Result :: {Results, ElvisCfg, File}.
apply_rule({Module, Function}, {Result, Config, File}) ->
apply_rule({Module, Function, #{}}, {Result, Config, File});
apply_rule({Module, Function, ConfigArgs}, {Result, Config, File}) ->
ConfigMap =
try
ensure_config_map(Module, Function, ConfigArgs)
catch
_:function_clause ->
throw({invalid_config, disable_without_ruleset})
end,
RuleResult =
try
AnalyzedModule = elvis_file:module(File),
Ignores = maps:get(ignore, ConfigMap, []),
case lists:member(AnalyzedModule, Ignores) of
false ->
FilteredConfigMap
= maps:merge(ConfigMap#{ ignore => lists:delete(AnalyzedModule, Ignores) },
ConfigArgs),
Results = Module:Function(Config, File, FilteredConfigMap),
SortFun = fun(#{line_num := L1}, #{line_num := L2}) ->
L1 =< L2
end,
SortResults = lists:sort(SortFun, Results),
elvis_result:new(rule, Function, SortResults);
true ->
elvis_result:new(rule, Function, [])
end
catch
_:Reason ->
Msg = "'~p' while applying rule '~p'.",
elvis_result:new(error, Msg, [Reason, Function])
end,
{[RuleResult | Result], Config, File}.
%% @doc Process a tules configuration argument and converts it to a map.
ensure_config_map(_, _, Map) when is_map(Map) ->
Map;
ensure_config_map(elvis_style, line_length, [Limit]) ->
#{limit => Limit};
ensure_config_map(elvis_style, operator_spaces, Rules) ->
#{rules => Rules};
ensure_config_map(elvis_style, nesting_level, [Level]) ->
#{level => Level};
ensure_config_map(elvis_style, god_modules, [Limit]) ->
#{limit => Limit};
ensure_config_map(elvis_style, god_modules, [Limit, IgnoreModules]) ->
#{limit => Limit, ignore => IgnoreModules};
ensure_config_map(elvis_style, invalid_dynamic_call, IgnoreModules) ->
#{ignore => IgnoreModules};
ensure_config_map(elvis_style,
module_naming_convention,
[Regex, IgnoreModules]) ->
#{regex => Regex, ignore => IgnoreModules};
ensure_config_map(_, _, []) ->
#{}.
%% @private
elvis_result_status(Results) ->
case elvis_result:status(Results) of
fail -> {fail, elvis_result:clean(Results)};
ok -> ok
end.