Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Most, if not all, of the rules will present (opinionated) documentation sections
- [Param Pattern-Matching](doc_rules/elvis_style/param_pattern_matching.md)
- [Prefer Unquoted Atoms](doc_rules/elvis_text_style/prefer_unquoted_atoms.md)
- [Private Data Types](doc_rules/elvis_style/private_data_types.md)
- [Simplify Anonymous Functions](doc_rules/elvis_style/simplify_anonymous_functions.md)
- [State Record And Type](doc_rules/elvis_style/state_record_and_type.md)
- [Variable Casing](doc_rules/elvis_style/variable_casing.md)
- [Variable Naming Convention](doc_rules/elvis_style/variable_naming_convention.md)
Expand Down
32 changes: 32 additions & 0 deletions doc_rules/elvis_style/simplify_anonymous_functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Simplify Anonymous Functions [![](https://img.shields.io/badge/since-4.2.0-blue)](https://github.com/inaka/elvis_core/releases/tag/4.2.0) ![](https://img.shields.io/badge/BEAM-yes-orange)

Anonymous functions that simply call a named function (with the same arguments in the same order)
should be avoided; they can be more concisely expressed using the function reference syntax.

## Avoid

```erlang
fun(Pattern) -> is_match_node(Pattern) end
```

## Prefer

```erlang
fun is_match_node/1
```

## Rationale

The `fun F/A` syntax is clearer and more concise when the anonymous function does nothing more than
call another function. It reduces noise and makes the code easier to read and maintain by removing
unnecessary bindings and boilerplate.

## Options

- None.

## Example configuration

```erlang
{elvis_style, simplify_anonymous_functions, #{}}
```
3 changes: 2 additions & 1 deletion src/elvis_ruleset.erl
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ elvis_style_rules() ->
elvis_rule:new(elvis_style, private_data_types),
elvis_rule:new(elvis_style, no_used_ignored_variables),
elvis_rule:new(elvis_style, variable_naming_convention),
elvis_rule:new(elvis_style, guard_operators)
elvis_rule:new(elvis_style, guard_operators),
elvis_rule:new(elvis_style, simplify_anonymous_functions)
].

erl_files_test_rules() ->
Expand Down
54 changes: 52 additions & 2 deletions src/elvis_style.erl
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
no_boolean_in_comparison/2,
no_operation_on_same_value/2,
no_receive_without_timeout/2,
guard_operators/2
guard_operators/2,
simplify_anonymous_functions/2
]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -1544,7 +1545,7 @@ is_list_node(#{type := cons}) ->
is_list_node(#{type := nil}) ->
true;
is_list_node(#{type := match, content := Content}) ->
lists:any(fun(Elem) -> is_list_node(Elem) end, Content);
lists:any(fun is_list_node/1, Content);
is_list_node(_) ->
false.

Expand Down Expand Up @@ -1666,6 +1667,10 @@ same_value_on_both_sides(Node) ->
false
end.

nodes_same_except_location([_ | _], []) ->
false;
nodes_same_except_location([], [_ | _]) ->
false;
nodes_same_except_location([], []) ->
true;
nodes_same_except_location([LeftNode | LeftNodes], [RightNode | RigthNodes]) ->
Expand Down Expand Up @@ -2175,6 +2180,51 @@ always_shortcircuit(Rule, ElvisConfig) ->
|| OpNode <- OpNodes
].

simplify_anonymous_functions(Rule, ElvisConfig) ->
{nodes, FunNodes} = elvis_code:find(#{
of_types => ['fun'],
inside => elvis_code:root(Rule, ElvisConfig),
filtered_by => fun is_simple_anonymous_function/1,
traverse => all
}),

[
elvis_result:new_item(
"an unnecessary anonymous function wrapper was found; prefer 'fun M:F/A'",
[],
#{node => Fun}
)
|| Fun <- FunNodes
].

%% @doc Has this anonymous function just one clause that is a call to a
%% regular function with the same args? i.e., something like
%% fun() -> x() end ; or
%% fun(A) -> some:funct(A) end ; or
%% fun(A, B, C) -> x:y(A, B, C) end
%% Note that we assume that FunNode is, in fact, an anonymous function node.
is_simple_anonymous_function(FunNode) ->
case
elvis_code:find(#{
of_types => [clause],
inside => FunNode
})
of
{nodes, [Clause]} ->
case ktn_code:content(Clause) of
[Expression] ->
ktn_code:node_attr(guards, Clause) =:= [] andalso
ktn_code:type(Expression) =:= call andalso
nodes_same_except_location(
ktn_code:node_attr(pattern, Clause), ktn_code:content(Expression)
);
_ ->
false
end;
_ ->
false
end.

export_used_types(Rule, ElvisConfig) ->
Root = elvis_code:root(Rule, ElvisConfig),

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

-export([functions/0]).

functions() ->
#{
no_args => fun() -> rand:uniform() end,
one_arg => fun(X) -> erlang:display(X) end,
two_args => fun(A, B) -> io:format(A, B) end,
local => fun() -> local() end,
auto_import => fun(A) -> atom_to_list(A) end
}.

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

-export([functions/0]).

functions() ->
#{
no_args => fun rand:uniform/0,
one_arg => fun erlang:display/1,
two_args => fun io:format/2,
local => fun local/0,
auto_import => fun atom_to_list/1,
flip_param_order => fun(A, B) -> io:format(B, A) end,
more_than_a_call => fun() -> second:call(first:call()) end,
op => fun(X) -> not X end,
bi_op => fun(A, B) -> A + B end,
id => fun(X) -> X end,
with_guards => fun(X) when is_binary(X) -> binary_to_list(X) end,
with_matching => fun(<<X/binary>>) -> erlang:binary_to_atom(X) end,
multiple_clauses => fun (x) -> atom_to_list(x); (X) -> binary_to_list(X) end,
ignored_arg => fun(X, _) -> erlang:display(X) end
}.

local() -> local.
34 changes: 32 additions & 2 deletions test/style_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
verify_ms_transform_included/1,
verify_no_boolean_in_comparison/1,
verify_no_operation_on_same_value/1,
verify_no_receive_without_timeout/1
verify_no_receive_without_timeout/1,
verify_simplify_anonymous_functions/1
]).
%% -elvis attribute
-export([
Expand Down Expand Up @@ -166,7 +167,8 @@ groups() ->
verify_no_match_in_condition,
verify_behaviour_spelling,
verify_param_pattern_matching,
verify_private_data_types
verify_private_data_types,
verify_simplify_anonymous_functions
]}
].

Expand Down Expand Up @@ -1235,6 +1237,34 @@ verify_always_shortcircuit(Config) ->
Config, elvis_style, always_shortcircuit, #{}, PathPass
).

verify_simplify_anonymous_functions(Config) ->
Group = proplists:get_value(group, Config, erl_files),
Ext = proplists:get_value(test_file_ext, Config, "erl"),

PathFail = "fail_simplify_anonymous_functions." ++ Ext,
Warnings =
elvis_test_utils:elvis_core_apply_rule(
Config, elvis_style, simplify_anonymous_functions, #{}, PathFail
),

case Group of
beam_files ->
[_, _, _, _, _] = Warnings;
_ ->
[
#{line_num := 7},
#{line_num := 8},
#{line_num := 9},
#{line_num := 10},
#{line_num := 11}
] = Warnings
end,

PathPass = "pass_simplify_anonymous_functions." ++ Ext,
[] = elvis_test_utils:elvis_core_apply_rule(
Config, elvis_style, simplify_anonymous_functions, #{}, PathPass
).

verify_no_spec_with_records(Config) ->
Ext = proplists:get_value(test_file_ext, Config, "erl"),

Expand Down
Loading