Current section

Files

Jump to
elvis_core src elvis_project.erl
Raw

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.