Skip to content

Commit

Permalink
Merge pull request #277 from kbaird/Issue#186-FunctionNames-Refactoring
Browse files Browse the repository at this point in the history
Issue#186 function names refactoring
  • Loading branch information
Brujo Benavides committed Sep 28, 2015
2 parents ad797a0 + 8a10acc commit 0f091dd
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 7 deletions.
5 changes: 5 additions & 0 deletions config/elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
#{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$",
ignore => []}
},
{
elvis_style,
function_naming_convention,
#{regex => "^([a-z][a-z0-9]*_?)*$"}
},
{elvis_style, state_record_and_type},
{elvis_style, no_spec_with_records},
{elvis_style, dont_repeat_yourself, #{min_complexity => 10}}
Expand Down
30 changes: 23 additions & 7 deletions src/elvis_code.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
-export([
past_nesting_limit/2,
exported_functions/1,
function_names/1,
module_name/1,
print_node/1,
print_node/2
Expand Down Expand Up @@ -191,15 +192,18 @@ module_name(#{type := root, content := Content}) ->
[] -> undefined
end.

%% @doc Takes the root node of a parse_tree and returns name and artity
%% of each exported functions.
%% @doc Takes the root node of a parse_tree and returns name and arity
%% of each exported function.
-spec exported_functions(ktn_code:tree_node()) -> [{atom(), integer()}].
exported_functions(#{type := root, content := Content}) ->
Fun = fun
(Node = #{type := export}) ->
ktn_code:attr(value, Node);
(_) -> []
end,
Fun = make_extractor_fun(exported_functions),
lists:flatmap(Fun, Content).

%% @doc Takes the root node of a parse_tree and returns the name
%% of each function, whether exported or not.
-spec function_names(ktn_code:tree_node()) -> [{atom(), integer()}].
function_names(#{type := root, content := Content}) ->
Fun = make_extractor_fun(function_names),
lists:flatmap(Fun, Content).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand All @@ -222,3 +226,15 @@ level_increment(Type) ->
true -> 1;
false -> 0
end.

%% @private
%% @doc Returns an anonymous Fun to be flatmapped over node content, as
%% appropriate for the exported function whose name is the argument given.
make_extractor_fun(exported_functions) ->
fun (Node = #{type := export}) -> ktn_code:attr(value, Node);
(_) -> []
end;
make_extractor_fun(function_names) ->
fun (Node = #{type := function}) -> [ktn_code:attr(name, Node)];
(_) -> []
end.
32 changes: 32 additions & 0 deletions src/elvis_style.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-module(elvis_style).

-export([
function_naming_convention/3,
line_length/3,
no_tabs/3,
no_spaces/3,
Expand Down Expand Up @@ -68,6 +69,10 @@
"Use the '-callback' attribute instead of 'behavior_info/1' "
"on line ~p.").

-define(FUNCTION_NAMING_CONVENTION_MSG,
"The function ~p does not respect the format defined by the "
"regular expression '~p'.").

-define(MODULE_NAMING_CONVENTION_MSG,
"The module ~p does not respect the format defined by the "
"regular expression '~p'.").
Expand Down Expand Up @@ -102,6 +107,33 @@

-type empty_rule_config() :: #{}.

-type function_naming_convention_config() :: #{regex => string(),
ignore => [module()]
}.

-spec function_naming_convention(elvis_config:config(),
elvis_file:file(),
function_naming_convention_config()) ->
[elvis_result:item()].
function_naming_convention(Config, Target, RuleConfig) ->
Regex = maps:get(regex, RuleConfig, ".*"),

{Root, _} = elvis_file:parse_tree(Config, Target),
FunctionNames = elvis_code:function_names(Root),
errors_for_function_names(Regex, FunctionNames).

errors_for_function_names(_Regex, []) -> [];
errors_for_function_names(Regex, [FunctionName | RemainingFuncNames]) ->
FunctionNameStr = atom_to_list(FunctionName),
case re:run(FunctionNameStr, Regex) of
nomatch ->
Msg = ?FUNCTION_NAMING_CONVENTION_MSG,
Info = [FunctionNameStr, Regex],
Result = elvis_result:new(item, Msg, Info, 1),
[Result | errors_for_function_names(Regex, RemainingFuncNames)];
{match, _} -> errors_for_function_names(Regex, RemainingFuncNames)
end.

-type line_length_config() :: #{limit => integer(),
skip_comments => false | any | whole_line
}.
Expand Down
37 changes: 37 additions & 0 deletions test/examples/fail_function_naming_convention.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-module(fail_function_naming_convention).

-export([bad_names_inside/0]).

%% Function names must use only lowercase characters.
%% Words in function names must be separated with _.

%% Cf. https://github.com/inaka/erlang_guidelines#function-names

bad_names_inside() ->
camelCase(should, fail),
'ALL_CAPS'(should, fail),
'Initial_cap'(should, fail),
'ok-for-lisp'(should, fail),
'no_predicates?'(should, fail),
user@location(should, fail).

%% Private / hidden functions still checked

camelCase(Should, Fail) ->
[Should, Fail].

'ALL_CAPS'(Should, Fail) ->
[Should, Fail].

'Initial_cap'(Should, Fail) ->
[Should, Fail].

'ok-for-lisp'(Should, Fail) ->
[Should, Fail].

'no_predicates?'(Should, Fail) ->
[Should, Fail].

user@location(Should, Fail) ->
[Should, Fail].

15 changes: 15 additions & 0 deletions test/examples/pass_function_naming_convention.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-module(pass_function_naming_convention).

-export([snake_case/2]).

%% Function names must use only lowercase characters.
%% Words in function names must be separated with _.

%% Cf. https://github.com/inaka/erlang_guidelines#function-names

has_digit1(Should, Pass) ->
[Should, Pass].

snake_case(Should, Pass) ->
[Should, Pass].

19 changes: 19 additions & 0 deletions test/style_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
]).

-export([
verify_function_naming_convention/1,
verify_line_length_rule/1,
verify_no_tabs_rule/1,
verify_no_spaces_rule/1,
Expand Down Expand Up @@ -67,6 +68,24 @@ end_per_suite(Config) ->
%%%%%%%%%%%%%%%
%%% Rules

-spec verify_function_naming_convention(config()) -> any().
verify_function_naming_convention(_Config) ->
ElvisConfig = elvis_config:default(),
SrcDirs = elvis_config:dirs(ElvisConfig),

RuleConfig = #{regex => "^([a-z][a-z0-9]*_?)*$"},

PathPass = "pass_function_naming_convention.erl",
{ok, FilePass} = elvis_test_utils:find_file(SrcDirs, PathPass),
[] =
elvis_style:function_naming_convention(ElvisConfig, FilePass, RuleConfig),

PathFail = "fail_function_naming_convention.erl",
{ok, FileFail} = elvis_test_utils:find_file(SrcDirs, PathFail),
[_CamelCaseError, _ALL_CAPSError, _InitialCapError,
_HyphenError, _PredError, _EmailError] =
elvis_style:function_naming_convention(ElvisConfig, FileFail, RuleConfig).

-spec verify_line_length_rule(config()) -> any().
verify_line_length_rule(_Config) ->
ElvisConfig = elvis_config:default(),
Expand Down

0 comments on commit 0f091dd

Please sign in to comment.