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_config.erl
-module(elvis_config).
-export([ from_rebar/1
, from_file/1
, from_application_or_config/2
, validate/1
, normalize/1
%% Geters
, dirs/1
, ignore/1
, filter/1
, files/1
, rules/1
%% Files
, resolve_files/1
, resolve_files/2
, apply_to_files/2
%% Rules
, merge_rules/2
]).
-export_type([config/0]).
-export_type([configs/0]).
-type config() :: map().
-type configs() :: [config()].
-define(DEFAULT_FILTER, "*.erl").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec from_rebar(string()) -> configs().
from_rebar(Path) ->
case file:consult(Path) of
{ok, AppConfig} ->
load(config, load_initial(AppConfig), []);
{error, Reason} ->
throw(Reason)
end.
-spec from_file(string()) -> configs().
from_file(Path) ->
from_file(Path, config, []).
-spec from_file(string(), atom(), term()) -> configs().
from_file(Path, Key, Default) ->
case file:consult(Path) of
{ok, [AppConfig]} ->
load(Key, load_initial(AppConfig), Default);
{error, {_Line, _Mod, _Term} = Reason} ->
throw(Reason);
{error, _Reason} ->
Default
end.
-spec from_application_or_config(atom(), term()) -> term().
from_application_or_config(Key, Default) ->
case application:get_env(elvis_core, Key) of
{ok, Value} ->
Value;
_ ->
from_file("elvis.config", Key, Default)
end.
-spec load(atom(), term(), term()) -> configs().
load(Key, ElvisConfig, Default) ->
ensure_config_list(Key, proplists:get_value(Key, ElvisConfig, Default)).
-spec load_initial(term()) -> [term()].
load_initial(AppConfig) ->
ElvisConfig = proplists:get_value(elvis, AppConfig, []),
RulesetsConfig = proplists:get_value(rulesets, ElvisConfig, #{}),
elvis_rulesets:set_rulesets(RulesetsConfig),
ElvisConfig.
ensure_config_list(config, Config) when is_map(Config) ->
[Config];
ensure_config_list(_Other, Config) ->
Config.
-spec validate(Config::configs()) -> ok.
validate([]) ->
throw({invalid_config, empty_config});
validate(Config) ->
lists:foreach(fun do_validate/1, Config).
do_validate(RuleGroup) ->
case maps:is_key(src_dirs, RuleGroup) or maps:is_key(dirs, RuleGroup) of
false -> throw({invalid_config, {missing_dirs, RuleGroup}});
true -> ok
end,
case maps:is_key(dirs, RuleGroup) of
true ->
case maps:is_key(filter, RuleGroup) of
false -> throw({invalid_config, {missing_filter, RuleGroup}});
true -> ok
end;
false -> ok
end,
case maps:is_key(rules, RuleGroup) orelse maps:is_key(ruleset, RuleGroup) of
false -> throw({invalid_config, {missing_rules, RuleGroup}});
true -> ok
end.
-spec normalize(configs()) -> configs().
normalize(Config) when is_list(Config) ->
lists:map(fun do_normalize/1, Config).
%% @private
do_normalize(Config = #{src_dirs := Dirs}) ->
%% NOTE: Provided for backwards compatibility.
%% Rename 'src_dirs' key to 'dirs'.
Config1 = maps:remove(src_dirs, Config),
Config1#{dirs => Dirs};
do_normalize(Config) ->
Config.
-spec dirs(Config::configs() | config()) -> [string()].
dirs(Config) when is_list(Config) ->
lists:flatmap(fun dirs/1, Config);
dirs(_RuleGroup = #{dirs := Dirs}) ->
Dirs;
dirs(#{}) ->
[].
-spec ignore(configs() | config()) -> [string()].
ignore(Config) when is_list(Config) ->
lists:flatmap(fun ignore/1, Config);
ignore(_RuleGroup = #{ignore := Ignore}) ->
lists:map(fun ignore_to_regexp/1, Ignore);
ignore(#{}) ->
[].
-spec filter(configs() | config()) -> [string()].
filter(Config) when is_list(Config) ->
lists:flatmap(fun filter/1, Config);
filter(_RuleGroup = #{filter := Filter}) ->
Filter;
filter(#{}) ->
?DEFAULT_FILTER.
-spec files(RuleGroup::configs() | config()) -> [elvis_file:file()].
files(RuleGroup) when is_list(RuleGroup) ->
lists:map(fun files/1, RuleGroup);
files(_RuleGroup = #{files := Files}) ->
Files;
files(#{}) ->
[].
-spec rules(RulesL::configs()) -> [[elvis_core:rule()]];
(Rules::config()) -> [elvis_core:rule()].
rules(Rules) when is_list(Rules) ->
lists:map(fun rules/1, Rules);
rules(#{rules := UserRules, ruleset := RuleSet}) ->
DefaultRules = elvis_rulesets:rules(RuleSet),
merge_rules(UserRules, DefaultRules);
rules(#{rules := Rules}) -> Rules;
rules(#{ruleset := RuleSet}) ->
elvis_rulesets:rules(RuleSet);
rules(#{}) ->
[].
%% @doc Takes a configuration and a list of files, filtering some
%% of them according to the 'filter' key, or if not specified
%% uses '*.erl'.
%% @end
%% resolve_files/2 with a configs() type is used in elvis project
-spec resolve_files(Config::configs() | config(), Files::[elvis_file:file()]) ->
configs() | config().
resolve_files(Config, Files) when is_list(Config) ->
Fun = fun(RuleGroup) -> resolve_files(RuleGroup, Files) end,
lists:map(Fun, Config);
resolve_files(RuleGroup, Files) ->
Filter = filter(RuleGroup),
Dirs = dirs(RuleGroup),
Ignore = ignore(RuleGroup),
FilteredFiles = elvis_file:filter_files(Files, Dirs, Filter, Ignore),
_ = case FilteredFiles of
[] ->
Ruleset = maps:get(ruleset, RuleGroup, undefined),
Error = elvis_result:new(warn, "Searching for files in ~p, for ruleset ~p, "
"with filter ~p, yielded none. "
"Update your configuration.",
[Dirs, Ruleset, Filter]),
ok = elvis_result:print_results([Error]);
_ ->
ok
end,
RuleGroup#{files => FilteredFiles}.
%% @doc Takes a configuration and finds all files according to its 'dirs'
%% end 'filter' key, or if not specified uses '*.erl'.
%% @end
-spec resolve_files(config()) -> config().
resolve_files(RuleGroup = #{files := _Files}) ->
RuleGroup;
resolve_files(RuleGroup = #{dirs := Dirs}) ->
Filter = filter(RuleGroup),
Files = elvis_file:find_files(Dirs, Filter),
resolve_files(RuleGroup, Files).
%% @doc Takes a function and configuration and applies the function to all
%% file in the configuration.
%% @end
-spec apply_to_files(Fun::fun(), Config::configs() | config()) -> configs() | config().
apply_to_files(Fun, Config) when is_list(Config) ->
ApplyFun = fun(RuleGroup) -> apply_to_files(Fun, RuleGroup) end,
lists:map(ApplyFun, Config);
apply_to_files(Fun, RuleGroup = #{files := Files}) ->
NewFiles = lists:map(Fun, Files),
RuleGroup#{files => NewFiles}.
%% @doc Ensures the ignore is a regexp, this is used
%% to allow using 'module name' atoms in the ignore
%% list by taking advantage of the fact that erlang
%% enforces the module and the file name to be the
%% same.
%% @end
-spec ignore_to_regexp(string() | atom()) -> string().
ignore_to_regexp(R) when is_list(R) ->
R;
ignore_to_regexp(A) when is_atom(A) ->
"/" ++ atom_to_list(A) ++ "\\.erl$".
%% @doc Merge user rules (override) with elvis default rules.
-spec merge_rules(UserRules::list(), DefaultRules::list()) -> [elvis_core:rule()].
merge_rules(UserRules, DefaultRules) ->
UnduplicatedRules =
% Drops repeated rules
lists:filter(
% If any default rule is in UserRules it means the user
% wants to override the rule.
fun({FileName, RuleName}) ->
not is_rule_override(FileName, RuleName, UserRules);
({FileName, RuleName, _}) ->
not is_rule_override(FileName, RuleName, UserRules);
(_) -> false
end,
DefaultRules
),
OverrideRules =
% Remove the rules that the user wants to "disable" and after that,
% remains just the rules the user wants to override.
lists:filter(
fun({_FileName, _RuleName, OverrideOptions}) ->
disable /= OverrideOptions;
(_) -> false end,
UserRules
),
UnduplicatedRules ++ OverrideRules.
-spec is_rule_override(FileName::atom(), RuleName::atom(), UserRules::[elvis_core:rule()]) ->
boolean().
is_rule_override(FileName, RuleName, UserRules) ->
lists:any(
fun(UserRule) ->
case UserRule of
{FileName, RuleName, _} -> true;
_ -> false
end
end,
UserRules
).