Packages
elvis_core
5.0.0
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
Retired package: Release invalid - We found a bug that needs fixing before using this release in rebar3_lint/elvis
Current section
Files
Jump to
Current section
Files
src/elvis_config.erl
-module(elvis_config).
-feature(maybe_expr, enable).
-elvis([
{elvis_style, abc_size, #{ignore => [{elvis_config, default_for, 1}]}},
{elvis_style, code_complexity, #{ignore => [{elvis_config, default_for, 1}]}}
]).
-export([from_rebar/1, from_file/1, validate_config/1, default/0]).
%% Getters
-export([files/1, rules/1, ruleset/1]).
%% Files
-export([resolve_files/1, resolve_files/2, apply_to_files/2]).
%% Rules
-export([merge_rules/2]).
%% Options
-export([config/0, output_format/0, verbose/0, no_output/0, parallel/0, warnings_as_errors/0]).
-export([
set_output_format/1, set_verbose/1, set_no_output/1, set_parallel/1, set_warnings_as_errors/1
]).
% Corresponds to each config map in the 'config' key.
-opaque t() ::
#{
files := [nonempty_string()],
ruleset := atom(),
resolved_files => dynamic(),
ignore => [string()]
}.
-export_type([t/0]).
-type output_format() :: plain | colors | parsable.
-export_type([output_format/0]).
-type fail_validation() :: {error, Message :: string()}.
-export_type([fail_validation/0]).
% API exports, not consumed locally.
-ignore_xref([from_rebar/1, from_file/1, default/0, resolve_files/2, apply_to_files/2]).
-ignore_xref([
set_output_format/1, set_verbose/1, set_no_output/1, set_parallel/1, set_warnings_as_errors/1
]).
-ifdef(TEST).
-export([reset_validation/0]).
-export([inject_ignore/2]).
-export([file_globs/1]).
-export([ignore/1]).
-endif.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
from_static(config, {Type, Config}) ->
inject_ignore(do_from_static(config, {Type, Config}), git_check_ignore());
from_static(Key, {Type, Config}) ->
_ = elvis_utils:debug("fetching key '~s' from '~s' configuration", [Key, Type]),
do_from_static(Key, {Type, Config}).
do_from_static(Key, {Type, Config}) ->
_ = elvis_utils:debug("fetching key '~s' from '~s' configuration", [Key, Type]),
case proplists:get_value(Key, Config) of
undefined ->
_ = elvis_utils:debug(
"no value for key '~s' found in '~s' configuration; going with default", [
Key, Type
]
),
default(Key);
Value ->
_ = elvis_utils:debug("value for key '~s' found in '~s' configuration", [Key, Type]),
Value
end.
-spec config() -> [t()] | fail_validation().
config() ->
for(config).
-spec output_format() -> output_format().
output_format() ->
for(output_format).
-spec verbose() -> boolean().
verbose() ->
for(verbose).
-spec no_output() -> boolean().
no_output() ->
for(no_output).
-spec parallel() -> pos_integer().
parallel() ->
for(parallel).
-spec warnings_as_errors() -> boolean().
warnings_as_errors() ->
for(warnings_as_errors).
-spec set_output_format(output_format()) -> ok.
set_output_format(OutputFormat) ->
set_env(output_format, OutputFormat).
-spec set_verbose(boolean()) -> ok.
set_verbose(Verbose) ->
set_env(verbose, Verbose).
-spec set_no_output(boolean()) -> ok.
set_no_output(NoOutput) ->
set_env(no_output, NoOutput).
-spec set_parallel(pos_integer()) -> ok.
set_parallel(Parallel) ->
set_env(parallel, Parallel).
-spec set_warnings_as_errors(boolean()) -> ok.
set_warnings_as_errors(WarningsAsErrors) ->
set_env(warnings_as_errors, WarningsAsErrors).
set_env(Key, Value) ->
application:set_env(elvis_core, Key, Value).
default(Key) ->
case application:get_env(elvis_core, Key) of
undefined ->
_ = elvis_utils:debug(
"no value for key '~s' found in application environment; going with default",
[Key]
),
default_for(Key);
{ok, Value} ->
_ = elvis_utils:debug("value for key '~s' (~p) found in application environment", [
Key, Value
]),
Value
end.
for(Key) ->
maybe
AppDefault = default_for(app),
{ok, ElvisConfig} ?= consult_elvis_config("elvis.config"),
AppConfig =
case ElvisConfig of
AppDefault ->
% This might happen whether we fail to parse the file or it actually is []
_ = elvis_utils:debug(
"elvis.config is unusable; falling back to rebar.config", []
),
consult_rebar_config("rebar.config");
AppConfig0 ->
AppConfig0
end,
% If we got this far, the configuration is valid...
% i.e. the return won't be {error, _}
from_static(Key, {app, AppConfig})
else
{error, _} = Error -> Error
end.
consult_elvis_config(File) ->
case file:consult(File) of
{ok, [AppConfig]} when is_list(AppConfig) ->
_ = elvis_utils:debug("elvis.config is consultable; using it", []),
{ok, AppConfig};
{error, {Line, Mod, Term}} ->
{error,
lists:flatten(
io_lib:format("elvis.config is unconsultable: ~p, ~p, ~p", [Line, Mod, Term])
)};
_ ->
_ = elvis_utils:debug("elvis.config is unconsultable", []),
{ok, default_for(app)}
end.
consult_rebar_config(File) ->
AppConfig =
case file:consult(File) of
{ok, AppConfig0} when is_list(AppConfig0) ->
_ = elvis_utils:debug("rebar.config is consultable; using it", []),
AppConfig0;
_ ->
_ = elvis_utils:debug("rebar.config os unconsultable", []),
default_for('rebar.config')
end,
from_static(elvis, {'rebar.config', AppConfig}).
-spec from_rebar(File :: string()) -> [t()] | fail_validation().
from_rebar(File) ->
AppConfig = consult_rebar_config(File),
fetch_elvis_config_from(AppConfig).
-spec from_file(File :: string()) -> [t()] | fail_validation().
from_file(File) ->
maybe
{ok, AppConfig} ?= consult_elvis_config(File),
fetch_elvis_config_from(AppConfig)
else
{error, _} = Error -> Error
end.
fetch_elvis_config_from(AppConfig) ->
try do_validate({app, AppConfig}) of
ok ->
from_static(config, {app, AppConfig})
catch
{invalid_config, Message} ->
{error, lists:flatten(Message)}
end.
default_for(app) ->
% This is the top-level element
[];
default_for('rebar.config') ->
[{elvis, []}];
default_for(config) ->
[];
default_for(output_format) ->
colors;
default_for(verbose) ->
false;
default_for(no_output) ->
false;
default_for(parallel) ->
erlang:system_info(schedulers_online);
default_for(warnings_as_errors) ->
true;
default_for(rulesets) ->
#{};
default_for([config, files]) ->
[];
default_for([config, ignore]) ->
[];
default_for([config, ruleset]) ->
undefined;
default_for([config, rules]) ->
[].
-spec default() -> [t()].
default() ->
[
#{
files => ["apps/**/src/*.erl", "src/**/*.erl"],
ruleset => erl_files
},
#{
files => ["apps/**/src/*.hrl", "apps/**/include/*.hrl", "src/*.hrl", "include/*.hrl"],
ruleset => hrl_files
},
#{files => ["rebar.config"], ruleset => rebar_config},
#{files => [".gitignore"], ruleset => gitignore}
].
-ifdef(TEST).
-spec file_globs(t()) -> [string()].
file_globs(#{files := Globs}) when is_list(Globs) ->
Globs;
file_globs(#{}) ->
[].
-endif.
-spec ignore([t()] | t()) -> [string()].
ignore(Config) when is_list(Config) ->
lists:flatmap(fun ignore/1, Config);
ignore(#{ignore := Ignore}) ->
lists:map(fun ignore_to_regexp/1, Ignore);
ignore(#{}) ->
[].
-spec files(RuleGroup :: [t()] | t()) -> [elvis_file:t()].
files(RuleGroup) when is_list(RuleGroup) ->
lists:map(fun files/1, RuleGroup);
files(#{resolved_files := Files}) ->
Files;
files(#{}) ->
[].
-spec rules
(RulesL :: [t()]) -> [[elvis_rule:t()]];
(Rules :: t()) -> [elvis_rule:t()].
rules(Rules) when is_list(Rules) ->
lists:map(fun rules/1, Rules);
rules(#{rules := UserRules, ruleset := Ruleset}) ->
DefaultRules = elvis_ruleset:rules(Ruleset),
merge_rules(UserRules, DefaultRules);
rules(#{rules := Rules}) ->
filter_removed_rules(Rules);
rules(#{ruleset := Ruleset}) ->
elvis_ruleset:rules(Ruleset);
rules(#{}) ->
[].
-spec ruleset(t()) -> Ruleset :: atom() | undefined.
ruleset(ElvisConfig) ->
maps:get(ruleset, ElvisConfig, undefined).
%% @doc Takes a configuration and a list of files, filtering some
%% of them according to the 'files' globs and ignore list.
%% @end
%% resolve_files/2 with a [t()] type is used in elvis project
-spec resolve_files(Config :: [t()] | t(), Files :: [elvis_file:t()]) ->
[t()] | t().
resolve_files(Config, Files) when is_list(Config) ->
Fun = fun(RuleGroup) -> resolve_files(RuleGroup, Files) end,
lists:map(Fun, Config);
resolve_files(RuleGroup, Files) when is_map(RuleGroup) ->
FileGlobs = maps:get(files, RuleGroup, []),
Ignore = ignore(RuleGroup),
FilteredFiles = elvis_file:filter_files(Files, FileGlobs, Ignore),
RuleGroup#{resolved_files => FilteredFiles}.
%% @doc Takes a configuration and finds all files according to its 'files' globs.
%% @end
-spec resolve_files(t()) -> t().
resolve_files(#{resolved_files := _} = RuleGroup) ->
RuleGroup;
resolve_files(#{files := FileGlobs} = RuleGroup) ->
Ignore = ignore(RuleGroup),
FoundFiles = elvis_file:find_files(FileGlobs),
FilteredFiles = filter_by_ignore(FoundFiles, Ignore),
RuleGroup#{resolved_files => FilteredFiles}.
filter_by_ignore(Files, Ignore) ->
lists:filter(
fun(#{path := Path}) ->
not lists:any(
fun(Regex) -> match =:= re:run(Path, Regex, [{capture, none}]) end,
Ignore
)
end,
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 :: [t()] | t()) ->
[t()] | t().
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, #{resolved_files := Files} = RuleGroup) ->
NewFiles = lists:map(Fun, Files),
RuleGroup#{resolved_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_rule:t()].
merge_rules(UserRules0, DefaultRules) ->
UserRules = filter_removed_rules(UserRules0),
UnduplicatedRules =
% Drops repeated rules
% If any default rule is in UserRules it means the user
lists:filtermap(
% wants to override the rule.
fun(Tuple) ->
Rule = elvis_rule:from_tuple(Tuple),
case not is_rule_override(Rule, UserRules) andalso not elvis_rule:disabled(Rule) of
true ->
{true, Rule};
false ->
false
end
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:filtermap(
fun(Tuple) ->
Rule = elvis_rule:from_tuple(Tuple),
case elvis_rule:disabled(Rule) of
false ->
{true, Rule};
true ->
false
end
end,
UserRules
),
UnduplicatedRules ++ OverrideRules.
filter_removed_rules(RuleTuples) ->
lists:filter(
fun(RuleTuple) ->
case elvis_rule:is_valid_from_tuple(RuleTuple) of
{removed, _} ->
false;
_ ->
true
end
end,
RuleTuples
).
-spec is_rule_override(
Rule :: elvis_rule:t(),
UserRules :: [{NS :: module(), Name :: atom()}]
) ->
boolean().
is_rule_override(Rule, UserRules) ->
lists:any(
fun(UserRule) ->
elvis_rule:same(Rule, elvis_rule:from_tuple(UserRule))
end,
UserRules
).
-spec validate_config(term()) -> ok | {error, Message :: string()}.
validate_config(ElvisConfig) ->
try
do_validate({config, ElvisConfig})
catch
{invalid_config, Message} ->
{error, lists:flatten(Message)}
end.
get_elvis_opt(OptName, Elvis) ->
proplists:get_value(OptName, Elvis, default_for(OptName)).
elvis_control_opts() ->
[output_format, verbose, no_output, parallel, warnings_as_errors].
do_validate({app = _Option, Elvis}) ->
maybe
ok ?= is_nonempty_list("elvis.config", Elvis),
ok ?=
proplist_keys_are_in(
'elvis.config', Elvis, elvis_control_opts() ++ [rulesets, config]
),
OutputFormat = get_elvis_opt(output_format, Elvis),
ok ?= is_one_of(output_format, OutputFormat, [colors, plain, parsable]),
Verbose = get_elvis_opt(verbose, Elvis),
ok ?= is_boolean(verbose, Verbose),
NoOutput = get_elvis_opt(no_output, Elvis),
ok ?= is_boolean(no_output, NoOutput),
Parallel = get_elvis_opt(parallel, Elvis),
ok ?= is_pos_integer(parallel, Parallel),
WarningsAsErrors = get_elvis_opt(warnings_as_errors, Elvis),
ok ?= is_boolean(warnings_as_errors, WarningsAsErrors),
CustomRulesets = get_elvis_opt(rulesets, Elvis),
ok ?= are_valid_rulesets(rulesets, CustomRulesets),
Configset = get_elvis_opt(config, Elvis),
ok ?= is_valid_config(config, maps:keys(CustomRulesets), Configset),
_ = elvis_ruleset:load_custom(from_static(rulesets, {app, Elvis}))
else
{v, true} ->
ok;
{error, FormatData} ->
do_validate_throw(FormatData)
end;
do_validate({config = _Option, ElvisConfig}) ->
maybe
ok ?= is_valid_config(config, elvis_ruleset:custom_names(), ElvisConfig)
else
{v, true} ->
ok;
{error, FormatData} ->
do_validate_throw(FormatData)
end.
-spec do_validate_throw(_) -> no_return().
do_validate_throw(FormatData) ->
{Format, Data} =
case is_list(FormatData) of
false ->
FormatData;
true ->
% Get only first result, for now
% If we wanna be smarter we need to start concatenating readable strings
[{Format0, Data0} | _] = FormatData,
{Format0, Data0}
end,
throw({invalid_config, lists:flatten(io_lib:format(Format, Data))}).
is_nonempty_list(What, List) when not is_list(List) orelse List =:= [] ->
{error, {"'~s' is expected to exist and be a non-empty list.", [What]}};
is_nonempty_list(_What, _List) ->
ok.
proplist_keys_are_in(What, List, Keys) ->
Filtered = [Element || {Element, _} <- List, not lists:member(Element, Keys)],
case Filtered of
[] ->
ok;
_ ->
{error,
{"in '~s', the following keys are unknown: ~s.", [
What, elvis_utils:list_to_str(Filtered)
]}}
end.
is_one_of(What, Value, Possibilities) ->
case lists:member(Value, Possibilities) of
true ->
ok;
_ ->
{error,
{"'~s' is expected to be one of the following: ~s.", [
What, elvis_utils:list_to_str(Possibilities)
]}}
end.
is_boolean(_What, Value) when is_boolean(Value) ->
ok;
is_boolean(What, _Value) ->
{error, {"'~s' is expected to be a boolean.", [What]}}.
is_pos_integer(_What, Value) when is_integer(Value) andalso Value > 0 ->
ok;
is_pos_integer(What, _Value) ->
{error, {"'~s' is expected to be a positive integer.", [What]}}.
are_valid_rulesets(What, CustomRulesets) ->
maybe
ok ?= is_map(What, CustomRulesets),
ok ?= all_map_keys_are_atoms(What, CustomRulesets),
ok ?= all_custom_rulesets_have_valid_rules(What, CustomRulesets),
ok ?= no_default_ruleset_override(What, CustomRulesets)
else
{error, FormatData} ->
{error, FormatData}
end.
is_map(_What, Value) when is_map(Value) ->
ok;
is_map(What, _Value) ->
{error, {"'~s' is expected to be a map.", [What]}}.
all_map_keys_are_atoms(What, Map) ->
Filtered = [Key || Key <- maps:keys(Map), not is_atom(Key)],
case Filtered of
[] ->
ok;
_ ->
{error, {"in '~s', keys are expected to be atoms.", [What]}}
end.
all_custom_rulesets_have_valid_rules(What, CustomRulesets) ->
AccOut = maps:fold(
fun(CustomRuleset, RuleTuples, AccInO) ->
lists:foldl(
fun(RuleTuple, AccInI) ->
case elvis_rule:is_valid_from_tuple(RuleTuple) of
{true, _Rule} ->
AccInI;
{removed, Msg} ->
_ = elvis_utils:warn("~s Skipping.", [Msg]),
AccInI;
{false, ValidError} ->
[
{"in '~s', in ruleset '~s', " ++ ValidError, [What, CustomRuleset]}
| AccInI
]
end
end,
AccInO,
RuleTuples
)
end,
[],
CustomRulesets
),
case AccOut of
[] ->
ok;
_ ->
{error, lists:reverse(AccOut)}
end.
no_default_ruleset_override(What, CustomRulesets) ->
Filtered = [
CustomRuleset
|| CustomRuleset <- maps:keys(CustomRulesets), elvis_ruleset:is_defined(CustomRuleset)
],
case Filtered of
[] ->
ok;
_ ->
{error,
{
"in '~s', the following rulesets are not expected to be "
"named after a default ruleset: ~s.",
[
What, elvis_utils:list_to_str(Filtered)
]
}}
end.
is_valid_config(What, CustomRulesetNames, Configset0) ->
maybe
ok ?= is_nonempty_list(What, Configset0),
Configset = wrap_in_list(Configset0),
ok ?= all_configs_are_valid(What, CustomRulesetNames, Configset)
else
{error, FormatData} ->
{error, FormatData}
end.
wrap_in_list(Term) when is_list(Term) ->
Term;
wrap_in_list(Term) ->
[Term].
all_configs_are_valid(What, CustomRulesetNames, Configset) ->
{_PosNumber, ValidErrors} = lists:foldl(
fun(Config, {PosNumber, AccIn}) ->
AccOut =
case config_is_valid(CustomRulesetNames, Config) of
ok ->
AccIn;
{error, ValidError} ->
[
{"in '~s', at list position number ~w, " ++ ValidError, [
What, PosNumber
]}
| AccIn
]
end,
{PosNumber + 1, AccOut}
end,
{1, []},
Configset
),
case ValidErrors of
[] ->
ok;
_ ->
{error, lists:reverse(ValidErrors)}
end.
get_config_opt(OptName, Config, false = _Compulsory) ->
{ok, maps:get(OptName, Config, default_for([config, OptName]))};
get_config_opt(OptName, Config, true = _Compulsory) ->
case maps:get(OptName, Config, undefined) of
undefined ->
{error, io_lib:format("'~s' is a compulsory option.", [OptName])};
_GetWithDefault ->
get_config_opt(OptName, Config, false)
end.
config_is_valid(CustomRulesetNames, Config) ->
maybe
ok ?= map_keys_are_in(Config, [files, ignore, ruleset, rules]),
{ok, FileGlobs} ?= get_config_opt(files, Config, true),
ok ?= all_files_globs_are_valid(FileGlobs),
{ok, Ignore} ?= get_config_opt(ignore, Config, false),
ok ?= is_list_of_ignorables(ignore, Ignore),
{ok, Ruleset} ?= get_config_opt(ruleset, Config, false),
ok ?= defined_ruleset_is_custom_or_default(CustomRulesetNames, Ruleset),
{ok, Rules} ?= get_config_opt(rules, Config, false),
ok ?= all_rules_are_valid(rules, Rules),
ok ?= either_rules_is_nonempty_or_ruleset_is_defined(Rules, Ruleset)
else
{error, {Format, Args}} ->
{error, io_lib:format(Format, Args)};
{error, ValidError} ->
{error, ValidError}
end.
map_keys_are_in(Map, _Keys) when not is_map(Map) ->
{error, "element is expected to be a map."};
map_keys_are_in(Map, Keys) ->
Filtered = [Key || Key <- maps:keys(Map), not lists:member(Key, Keys)],
case Filtered of
[] ->
ok;
_ ->
{error,
io_lib:format("the following keys are unknown: ~s.", [
elvis_utils:list_to_str(Filtered)
])}
end.
all_files_globs_are_valid(FileGlobs) when not is_list(FileGlobs) orelse FileGlobs =:= [] ->
{error, "'files' is expected to be a non-empty list."};
all_files_globs_are_valid(FileGlobs) ->
case lists:all(fun(G) -> is_list(G) andalso G =/= [] end, FileGlobs) of
true ->
case lists:any(fun(G) -> filelib:wildcard(G) =/= [] end, FileGlobs) of
true ->
ok;
false ->
{error,
io_lib:format("no '<globs>' in '~s' yielded any files to analyse.", [
FileGlobs
])}
end;
false ->
{error, "'files' is expected to be a non-empty list of non-empty strings."}
end.
is_list_of_ignorables(What, List) when not is_list(List) ->
{error, io_lib:format("'~s' is expected to be a list.", [What])};
is_list_of_ignorables(What, List) ->
Filtered = [Element || Element <- List, not elvis_rule:is_ignorable(Element)],
case Filtered of
[] ->
ok;
_ ->
{error,
io_lib:format("in '~s', the following elements are not ignorable: ~s.", [
What, elvis_utils:list_to_str(Filtered)
])}
end.
defined_ruleset_is_custom_or_default(_CustomRulesetNames, undefined = _Ruleset) ->
ok;
defined_ruleset_is_custom_or_default(CustomRulesetNames, Ruleset) ->
case lists:member(Ruleset, CustomRulesetNames) orelse elvis_ruleset:is_defined(Ruleset) of
true ->
ok;
_ ->
{error,
io_lib:format("'~s' is expected to be either a custom or a default ruleset.", [
Ruleset
])}
end.
all_rules_are_valid(What, RuleTuples) when not is_list(RuleTuples) ->
{error, io_lib:format("'~s' is expected to be a list.", [What])};
all_rules_are_valid(What, RuleTuples) ->
AccOut = lists:foldl(
fun(RuleTuple, AccInI) ->
case elvis_rule:is_valid_from_tuple(RuleTuple) of
{true, Rule} ->
check_rule_for_options(Rule, AccInI);
{removed, Msg} ->
_ = elvis_utils:warn("~s Skipping.", [Msg]),
AccInI;
{false, ValidError} ->
[io_lib:format("in '~s', " ++ ValidError, [What]) | AccInI]
end
end,
[],
RuleTuples
),
case AccOut of
[] ->
ok;
_ ->
{error, lists:reverse(AccOut)}
end.
either_rules_is_nonempty_or_ruleset_is_defined([_ | _] = _Rules, _Ruleset) ->
ok;
either_rules_is_nonempty_or_ruleset_is_defined(_Rules, Ruleset) when Ruleset =/= undefined ->
ok;
either_rules_is_nonempty_or_ruleset_is_defined(_Rules, _Ruleset) ->
{error, "either 'rules' is a non-empty list or 'ruleset' is defined."}.
check_rule_for_options(Rule, AccInI) ->
case elvis_rule:defkeys(Rule) of
[] ->
% No further validation possible.
AccInI;
DefKeysInput ->
NS = elvis_rule:ns(Rule),
Name = elvis_rule:name(Rule),
% Bypass new/ constraints.
DefKeys = [ignore | maps:keys(NS:default(Name))],
case DefKeysInput -- DefKeys of
[] ->
AccInI;
Extra ->
[
io_lib:format(
"in rule ~w/~w, the following options are unknown: ~s.",
[NS, Name, elvis_utils:list_to_str(Extra)]
),
AccInI
]
end
end.
-spec inject_ignore([t()], [string()]) -> [t()].
inject_ignore(Configs, GitIgnored) ->
[inject_gitignore(C, GitIgnored) || C <- Configs].
inject_gitignore(#{ignore := Ignore} = Config, GitIgnored) ->
Config#{ignore => Ignore ++ GitIgnored};
inject_gitignore(Config, GitIgnored) ->
Config#{ignore => GitIgnored}.
git_check_ignore() ->
maybe
GitExecutable = os:find_executable("git"),
true ?= is_list(GitExecutable),
RevParse = os:cmd("git rev-parse --is-inside-work-tree 2>&1"),
true ?= lists:prefix("true", RevParse),
GitCheckIgnore = do_git_check_ignore(),
string:lexemes(GitCheckIgnore, "\r\n")
else
_ -> []
end.
do_git_check_ignore() ->
case os:type() of
{win32, _} ->
os:cmd("git check-ignore '*'");
_ ->
os:cmd("git check-ignore **/*")
end.
-ifdef(TEST).
reset_validation() ->
[persistent_term:erase(K) || {{elvis_config_validation, _} = K, _} <- persistent_term:get()],
ok.
-endif.