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_project.erl
-module(elvis_project).
-export([
default/1,
no_deps_master_erlang_mk/3,
no_deps_master_rebar/3,
protocol_for_deps_erlang_mk/3,
git_for_deps_erlang_mk/3,
protocol_for_deps_rebar/3,
git_for_deps_rebar/3,
old_configuration_format/3
]).
-define(DEP_MASTER,
"Dependency '~s' revision is specified 'master', "
"please change this to a tag, branch or specific "
"commit.").
-define(DEP_NO_GIT,
"Dependency '~s' is not using appropriate protocol, "
"please change this to something like '~s'").
-define(OLD_CONFIG_FORMAT,
"The current Elvis configuration file has an outdated format. "
"Please check Elvis's GitHub repository to find out what the "
"new format is.").
% These are part of a non-declared "behaviour"
% The reason why we don't try to handle them with different arity is
% that arguments are ignored in different positions (1 and 3) so that'd
% probably be messier than to ignore the warning
-hank([{unnecessary_function_arguments, [
old_configuration_format/3,
no_deps_master_rebar/3,
no_deps_master_erlang_mk/3,
protocol_for_deps_rebar/3,
protocol_for_deps_erlang_mk/3
]}]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Default values
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec default(Rule :: atom()) -> DefaultRuleConfig :: term().
default(no_deps_master_erlang_mk) ->
#{ ignore => []
};
default(protocol_for_deps_erlang_mk) ->
#{ ignore => [],
regex => "(https://.*|[0-9]+([.][0-9]+)*)"
};
default(no_deps_master_rebar) ->
#{ ignore => []
};
default(protocol_for_deps_rebar) ->
#{ ignore => [],
regex => "(https://.*|[0-9]+([.][0-9]+)*)"
};
default(old_configuration_format) ->
#{}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-type protocol_for_deps_erlang_mk_config() :: #{ignore => [module()],
regex => string()}.
%% Deprecated
-spec git_for_deps_erlang_mk(elvis_config:config(),
elvis_file:file(),
protocol_for_deps_erlang_mk_config()) ->
[elvis_result:item()].
git_for_deps_erlang_mk(Config, Target, RuleConfig) ->
elvis_utils:error_prn("This rule has been deprecated please use "
"'protocol_for_deps_erlang_mk'."),
protocol_for_deps_erlang_mk(Config, Target, RuleConfig).
-spec protocol_for_deps_erlang_mk(elvis_config:config(),
elvis_file:file(),
protocol_for_deps_erlang_mk_config()) ->
[elvis_result:item()].
protocol_for_deps_erlang_mk(_Config, Target, RuleConfig) ->
IgnoreDeps = option(ignore, RuleConfig, protocol_for_deps_erlang_mk),
Regex = option(regex, RuleConfig, protocol_for_deps_erlang_mk),
Deps = get_erlang_mk_deps(Target),
BadDeps = lists:filter(fun(Dep) -> is_erlang_mk_not_git_dep(Dep, Regex) end,
Deps),
lists:flatmap(
fun(Line) ->
erlang_mk_dep_to_result(Line, ?DEP_NO_GIT, {IgnoreDeps, Regex})
end, BadDeps).
-type protocol_for_deps_rebar_config() :: #{ignore => [module()],
regex => string()}.
-spec git_for_deps_rebar(elvis_config:config(),
elvis_file:file(),
protocol_for_deps_rebar_config()) ->
[elvis_result:item()].
git_for_deps_rebar(Config, Target, RuleConfig) ->
elvis_utils:error_prn("This rule has been deprecated please use "
"'protocol_for_deps_rebar'."),
protocol_for_deps_rebar(Config, Target, RuleConfig).
-spec protocol_for_deps_rebar(elvis_config:config(),
elvis_file:file(),
protocol_for_deps_rebar_config()) ->
[elvis_result:item()].
protocol_for_deps_rebar(_Config, Target, RuleConfig) ->
IgnoreDeps = option(ignore, RuleConfig, protocol_for_deps_rebar),
Regex = option(regex, RuleConfig, protocol_for_deps_rebar),
Deps = get_rebar_deps(Target),
NoHexDeps = lists:filter(fun(Dep) -> not is_rebar_hex_dep(Dep) end,
Deps),
BadDeps = lists:filter(fun(Dep) -> is_rebar_not_git_dep(Dep, Regex) end,
NoHexDeps),
lists:flatmap(
fun(Line) ->
rebar_dep_to_result(Line, ?DEP_NO_GIT, {IgnoreDeps, Regex})
end, BadDeps).
-type no_deps_master_erlang_mk_config() :: #{ignore => [module()]}.
-spec no_deps_master_erlang_mk(elvis_config:config(),
elvis_file:file(),
no_deps_master_erlang_mk_config()) ->
[elvis_result:item()].
no_deps_master_erlang_mk(_Config, Target, RuleConfig) ->
IgnoreDeps = option(ignore, RuleConfig, no_deps_master_erlang_mk),
Deps = get_erlang_mk_deps(Target),
BadDeps = lists:filter(fun is_erlang_mk_master_dep/1, Deps),
lists:flatmap(
fun(Line) ->
erlang_mk_dep_to_result(Line, ?DEP_MASTER, IgnoreDeps)
end, BadDeps).
-type no_deps_master_rebar_config() :: #{ignore => [module()]}.
-spec no_deps_master_rebar(elvis_config:config(),
elvis_file:file(),
no_deps_master_rebar_config()) ->
[elvis_result:item()].
no_deps_master_rebar(_Config, Target, RuleConfig) ->
IgnoreDeps = option(ignore, RuleConfig, no_deps_master_rebar),
Deps = get_rebar_deps(Target),
BadDeps = lists:filter(fun is_rebar_master_dep/1, Deps),
lists:flatmap(
fun(Line) ->
rebar_dep_to_result(Line, ?DEP_MASTER, IgnoreDeps)
end, BadDeps).
-type empty_rule_config() :: #{}.
-spec old_configuration_format(elvis_config:config(),
elvis_file:file(),
empty_rule_config()) ->
[elvis_result:item()].
old_configuration_format(_Config, Target, _RuleConfig) ->
{Content, _} = elvis_file:src(Target),
[AllConfig] = ktn_code:consult(Content),
case proplists:get_value(elvis, AllConfig) of
undefined -> [];
ElvisConfig ->
case is_old_config(ElvisConfig) of
false -> [];
true ->
[elvis_result:new(item, ?OLD_CONFIG_FORMAT, [])]
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Rebar
get_rebar_deps(File) ->
{Src, _} = elvis_file:src(File),
Terms = ktn_code:consult(Src),
IsDepsTerm = fun
({deps, _}) -> true;
(_) -> false
end,
case lists:filter(IsDepsTerm, Terms) of
[] -> [];
[{deps, Deps}] -> Deps
end.
%% Rebar3
is_rebar_master_dep({_AppName, {_SCM, _Location, "master"}}) ->
true;
is_rebar_master_dep({_AppName, {_SCM, _Location, {branch, "master"}}}) ->
true;
is_rebar_master_dep({AppName, {raw, DepResourceSpecification}}) ->
is_rebar_master_dep({AppName, DepResourceSpecification});
%% Rebar2
is_rebar_master_dep({_AppName, _Vsn, {_SCM, _Location, "master"}}) ->
true;
is_rebar_master_dep({_AppName, _Vsn, {_SCM, _Location, {branch, "master"}}}) ->
true;
is_rebar_master_dep(_) ->
false.
is_rebar_hex_dep(_AppName) when is_atom(_AppName) ->
true;
is_rebar_hex_dep({_AppName, _Vsn, {pkg, _PackageName}})
when is_atom(_AppName),
is_list(_Vsn),
is_atom(_PackageName) ->
true;
is_rebar_hex_dep({_AppName, _Vsn})
when is_atom(_AppName),
is_list(_Vsn) ->
true;
is_rebar_hex_dep(_) ->
false.
is_rebar_not_git_dep({_AppName, {pkg, _OtherName}}, _Regex) ->
false;
is_rebar_not_git_dep({_AppName, {_SCM, Url, _Branch}}, Regex) ->
nomatch == re:run(Url, Regex, []);
is_rebar_not_git_dep({AppName, {raw, DepResourceSpecification}}, Regex) ->
is_rebar_not_git_dep({AppName, DepResourceSpecification}, Regex);
is_rebar_not_git_dep({_AppName, {_SCM, Url}}, Regex) ->
nomatch == re:run(Url, Regex, []);
is_rebar_not_git_dep({_AppName, _Vsn, {_SCM, Url}}, Regex) ->
nomatch == re:run(Url, Regex, []);
is_rebar_not_git_dep({_AppName, _Vsn, {_SCM, Url, _Branch}}, Regex) ->
nomatch == re:run(Url, Regex, []).
rebar_dep_to_result({AppName, _}, Message, {IgnoreDeps, Regex}) ->
case lists:member(AppName, IgnoreDeps) of
true -> [];
false -> [elvis_result:new(item, Message, [AppName, Regex])]
end;
rebar_dep_to_result({AppName, _}, Message, IgnoreDeps) ->
case lists:member(AppName, IgnoreDeps) of
true -> [];
false -> [elvis_result:new(item, Message, [AppName])]
end;
rebar_dep_to_result({AppName, _, GitInfo}, Message, {IgnoreDeps, Regex}) ->
rebar_dep_to_result({AppName, GitInfo}, Message, {IgnoreDeps, Regex});
rebar_dep_to_result({AppName, _, GitInfo}, Message, IgnoreDeps) ->
rebar_dep_to_result({AppName, GitInfo}, Message, IgnoreDeps).
%%% erlang.mk
is_erlang_mk_master_dep(Line) ->
case re:run(Line, "master *$", []) of
nomatch -> false;
_ -> true
end.
is_erlang_mk_not_git_dep(Line, Regex) ->
[_DepName, Dependency] = binary:split(Line, <<"=">>),
[_Protocol, Url | _] =
[Part
|| Part <- binary:split(Dependency, <<" ">>, [global, trim])
, Part /= <<>>],
nomatch == re:run(Url, Regex, []).
get_erlang_mk_deps(File) ->
{Src, _} = elvis_file:src(File),
Lines = elvis_utils:split_all_lines(Src),
Opts = [{capture, all_but_first, binary}],
IsDepsLine =
fun(Line) ->
case re:run(Line, "dep_([^ ]*)", Opts) of
nomatch ->
false;
{match, [Name]} ->
%% Filter out dep_depname_commit lines
binary:longest_common_suffix([Name, <<"_commit">>]) /= 7
end
end,
lists:filter(IsDepsLine, Lines).
erlang_mk_dep_to_result(Line, Message, {IgnoreDeps, Regex}) ->
Opts = [{capture, all_but_first, binary}],
{match, [Name]} = re:run(Line, "dep_([^ ]*)", Opts),
NameAtom = binary_to_atom(Name, utf8),
case lists:member(NameAtom, IgnoreDeps) of
true -> [];
false -> [elvis_result:new(item, Message, [Name, Regex])]
end;
erlang_mk_dep_to_result(Line, Message, IgnoreDeps) ->
Opts = [{capture, all_but_first, binary}],
{match, [Name]} = re:run(Line, "dep_([^ ]*)", Opts),
NameAtom = binary_to_atom(Name, utf8),
case lists:member(NameAtom, IgnoreDeps) of
true -> [];
false -> [elvis_result:new(item, Message, [Name])]
end.
%% Old config
is_old_config(ElvisConfig) ->
case proplists:get_value(config, ElvisConfig) of
undefined -> false;
Config when is_map(Config) -> true;
Config when is_list(Config) ->
SrcDirsIsKey = fun(RuleGroup) ->
maps:is_key(src_dirs, RuleGroup) orelse
exists_old_rule(RuleGroup)
end,
lists:filter(SrcDirsIsKey, Config) /= []
end.
exists_old_rule(#{rules := Rules}) ->
Filter = fun
({_, _, Args}) when is_list(Args) ->
true;
(_) ->
false
end,
lists:filter(Filter, Rules) /= [];
exists_old_rule(_) -> false.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Internal Function Definitions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec option(OptionName, RuleConfig, Rule) -> OptionValue
when OptionName :: atom(),
RuleConfig :: elvis_config:config(),
Rule :: atom(),
OptionValue :: term().
option(OptionName, RuleConfig, Rule) ->
maybe_default_option(maps:get(OptionName, RuleConfig, undefined), OptionName, Rule).
-spec maybe_default_option(UserDefinedOptionValue, OptionName, Rule) -> OptionValue
when UserDefinedOptionValue :: undefined | term(),
OptionName :: atom(),
Rule :: atom(),
OptionValue :: term().
maybe_default_option(undefined = _UserDefinedOptionValue, OptionName, Rule) ->
maps:get(OptionName, default(Rule));
maybe_default_option(UserDefinedOptionValue, _OptionName, _Rule) ->
UserDefinedOptionValue.