Current section

Files

Jump to
elvis_core src elvis_core.erl
Raw

src/elvis_core.erl

-module(elvis_core).
%% Public API
-export([
rock/0,
rock/1,
rock_this/1,
rock_this/2
]).
-export([start/0]).
-define(APP_NAME, "elvis").
-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),
ok.
%%% Rock Command
-spec rock() -> ok | {fail, [elvis_result:file()]}.
rock() ->
Config = elvis_config:default(),
rock(Config).
-spec rock(elvis_config:config()) -> ok | {fail, [elvis_result:file()]}.
rock(Config) ->
ok = elvis_config:validate(Config),
NewConfig = elvis_config:normalize(Config),
Results = lists:map(fun do_rock/1, NewConfig),
lists:foldl(fun combine_results/2, ok, Results).
-spec rock_this(target()) ->
ok | {fail, elvis_result:file()}.
rock_this(Target) ->
Config = elvis_config:default(),
rock_this(Target, Config).
-spec rock_this(target(), elvis_config:config()) ->
ok | {fail, elvis_result:file()}.
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,
FilteredConfig = lists:filter(FilterFun, NewConfig),
LoadedFile = load_file_data(FilteredConfig, File),
ApplyRulesFun = fun(Cfg) -> apply_rules(Cfg, LoadedFile) end,
Results = lists:map(ApplyRulesFun, FilteredConfig),
elvis_result_status(Results).
%% @private
-spec do_rock(map()) -> ok | {fail, [elvis_result:file()]}.
do_rock(Config0) ->
elvis_utils:info("Loading files..."),
Config = elvis_config:resolve_files(Config0),
Files = elvis_config:files(Config),
Fun = fun (File) -> load_file_data(Config, File) end,
LoadedFiles = lists:map(Fun, Files),
elvis_utils:info("Applying rules..."),
Results = [apply_rules(Config, File) || File <- LoadedFiles],
elvis_result_status(Results).
%% @private
-spec load_file_data(map() | [map()], 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 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}.
-spec apply_rules(map(), File::elvis_file:file()) ->
elvis_result:file().
apply_rules(Config, File) ->
Rules = elvis_config:rules(Config),
Acc = {[], Config, File},
{RulesResults, _, _} = lists:foldl(fun apply_rule/2, Acc, Rules),
Results = elvis_result:new(file, File, RulesResults),
elvis_result:print(Results),
Results.
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
Results = Module:Function(Config, File, ConfigMap),
SortFun = fun(#{line_num := L1}, #{line_num := L2}) ->
L1 =< L2
end,
SortResults = lists:sort(SortFun, Results),
elvis_result:new(rule, Function, SortResults)
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.