Packages
elvis_core
4.2.3
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]).
-export_type([target/0]).
-ifdef(TEST).
-export([apply_rule/2]).
% For tests (we can't Xref the tests because rebar3 fails to compile some files).
-ignore_xref([apply_rule/2]).
-endif.
% For eating our own dogfood.
-ignore_xref([main/1]).
% For internal use only
-ignore_xref([do_rock/2]).
% For shell usage.
-ignore_xref([start/0]).
% API exports, not consumed locally.
-ignore_xref([rock/1, rock_this/2]).
-type source_filename() :: nonempty_string().
-type target() :: source_filename() | module().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% @doc Used when starting the application on the shell.
-spec start() -> ok.
start() ->
{ok, _} = application:ensure_all_started(elvis_core),
ok.
validate_config(ElvisConfig) ->
try
elvis_config:validate_config(ElvisConfig)
catch
{invalid_config, _} = Caught ->
{error, {fail, [{throw, Caught}]}}
end.
%% In this context, `throw` means an error, e.g., validation or internal, not an actual
%% call to `erlang:throw/1`.
-spec rock([elvis_config:t()]) ->
ok | {fail, [{throw, term()} | elvis_result:file() | elvis_result:rule()]}.
rock(ElvisConfig) ->
case validate_config(ElvisConfig) of
ok ->
elvis_ruleset:drop_custom(),
Results = lists:map(fun do_parallel_rock/1, ElvisConfig),
lists:foldl(fun combine_results/2, ok, Results);
{error, Error} ->
Error
end.
-spec rock_this(target(), [elvis_config:t()]) ->
ok | {fail, [elvis_result:file() | elvis_result:rule()]}.
rock_this(Module, ElvisConfig) when is_atom(Module) ->
ModuleInfo = Module:module_info(compile),
Path = proplists:get_value(source, ModuleInfo),
rock_this(Path, ElvisConfig);
rock_this(Path, ElvisConfig) ->
case validate_config(ElvisConfig) of
ok ->
elvis_ruleset:drop_custom(),
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, ElvisConfig) of
[] ->
elvis_utils:info("Skipping ~s", [Path]);
FilteredElvisConfig ->
LoadedFile = load_file_data(FilteredElvisConfig, File),
ApplyRulesFun = fun(Cfg) -> apply_rules_and_print(Cfg, LoadedFile) end,
Results = lists:map(ApplyRulesFun, FilteredElvisConfig),
elvis_result_status(Results)
end;
{error, Error} ->
Error
end.
%% In this context, `throw` means an error, e.g., validation or internal, not an actual
%% call to `erlang:throw/1`.
-spec do_parallel_rock(elvis_config:t()) ->
ok
| {fail, [{throw, term()} | elvis_result:file() | elvis_result:rule()]}.
do_parallel_rock(ElvisConfig0) ->
Parallel = elvis_config:parallel(),
ElvisConfig = elvis_config:resolve_files(ElvisConfig0),
Files = elvis_config:files(ElvisConfig),
Result =
elvis_task:chunk_fold(
{?MODULE, do_rock},
fun(Elem, Acc) ->
elvis_result:print_results(Elem),
{ok, [Elem | Acc]}
end,
[],
[ElvisConfig],
Files,
Parallel
),
case Result of
{ok, Results} ->
elvis_result_status(Results);
{error, {T, E}} ->
%% {T, E} will be put into an {error, _} tuple higher on the call stack,
%% let's not encapsulate it multiple times.
{fail, [{T, E}]}
end.
-spec do_rock(elvis_file:t(), [elvis_config:t()] | elvis_config:t()) ->
{ok, elvis_result:file()}.
do_rock(File, ElvisConfig) ->
LoadedFile = load_file_data(ElvisConfig, File),
Results = apply_rules(ElvisConfig, LoadedFile),
{ok, Results}.
-spec load_file_data([elvis_config:t()] | elvis_config:t(), elvis_file:t()) ->
elvis_file:t().
load_file_data(ElvisConfig, File) ->
Path = elvis_file:path(File),
elvis_utils:info("Loading ~s", [Path]),
try
elvis_file:load_file_data(ElvisConfig, File)
catch
_:Reason ->
Msg = "~p when loading file ~p.",
elvis_utils:error(Msg, [Reason, Path]),
File
end.
-spec main([]) -> true | no_return().
main([]) ->
ok = application:load(elvis_core),
{module, _} = code:ensure_loaded(elvis_style),
case rock(elvis_config:config()) of
ok -> true;
_ -> elvis_utils:erlang_halt(1)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% 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(ElvisConfig, File) ->
Results = apply_rules(ElvisConfig, File),
elvis_result:print_results(Results),
Results.
-spec apply_rules(
[elvis_config:t()] | elvis_config:t(),
File :: elvis_file:t()
) ->
elvis_result:file().
apply_rules(ElvisConfig, File) ->
Rules = elvis_config:rules(ElvisConfig),
Acc = {[], ElvisConfig, File},
{ParseTree, _} = elvis_file:parse_tree(File, ElvisConfig),
{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) ->
{nodes, ElvisAttrs} =
elvis_code:find(#{
of_types => [elvis],
inside => ParseTree
}),
ElvisAttrRules = elvis_attr_rules(ElvisAttrs),
elvis_config:merge_rules(ElvisAttrRules, ElvisConfigRules).
elvis_attr_rules([] = _ElvisAttrs) ->
[];
elvis_attr_rules(ElvisAttrs) ->
[Rule || ElvisAttr <- ElvisAttrs, Rule <- ktn_code:attr(value, ElvisAttr)].
-spec apply_rule(Rule, {Results, ElvisConfig, File}) -> Result when
Rule :: elvis_rule:t(),
Results :: [elvis_result:rule() | elvis_result:elvis_error()],
ElvisConfig :: elvis_config:t(),
File :: elvis_file:t(),
Result :: {Results, ElvisConfig, File}.
apply_rule(Rule, {Result, ElvisConfig, File}) ->
RuleResult =
try
AnalyzedModule = elvis_file:module(File),
IgnorableModule = elvis_rule:ignorable(AnalyzedModule),
case elvis_rule:ignored(IgnorableModule, Rule) of
false ->
Results = elvis_rule:execute(elvis_rule:file(Rule, File), ElvisConfig),
SortFun = fun(#{line_num := L1}, #{line_num := L2}) -> L1 =< L2 end,
SortResults = lists:sort(SortFun, Results),
elvis_result:new(rule, Rule, SortResults);
true ->
elvis_result:new(rule, Rule, [])
end
catch
_:Reason:Stacktrace ->
Msg = "'~p' while applying rule '~p': ~p",
elvis_result:new(error, Msg, [
Reason, {elvis_rule:ns(Rule), elvis_rule:name(Rule)}, Stacktrace
])
end,
{[RuleResult | Result], ElvisConfig, File}.
elvis_result_status(Results) ->
case elvis_result:status(Results) of
fail ->
{fail, elvis_result:clean(Results)};
ok ->
ok
end.