Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New elvis_style rule: no_init_lists #367

Merged
merged 32 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
61a901d
add new rule: no_init_lists
bormilan Oct 6, 2024
6e914c0
add to RULES.md
bormilan Oct 6, 2024
e471724
fix rule settings, and message
bormilan Oct 7, 2024
f8dec95
delete print
bormilan Oct 7, 2024
ec36ca9
Merge branch 'main' into no-init-lists
bormilan Oct 7, 2024
6164e12
fix formatting
bormilan Oct 7, 2024
2d64012
fix deleted section
bormilan Oct 7, 2024
fffeed6
fix rule markdown
bormilan Oct 7, 2024
b517a00
fix rule markdown2
bormilan Oct 7, 2024
7972ccb
Update RULES.md
bormilan Oct 10, 2024
6348711
Update doc_rules/elvis_style/no_init_lists.md
bormilan Oct 10, 2024
68c8945
Update src/elvis_style.erl
bormilan Oct 10, 2024
70874c3
fix description and link of no_init_lists rule
bormilan Oct 10, 2024
0db27a1
Merge branch 'no-init-lists' of https://github.com/bormilan/elvis_cor…
bormilan Oct 10, 2024
299ed09
fix markdown
bormilan Oct 10, 2024
0653f03
little fixes and change logic
bormilan Oct 11, 2024
6513368
change maybe to case, refactor tests, and add to beam section too
bormilan Oct 15, 2024
f956cf9
fix maybe and no_init_lists msg
bormilan Oct 18, 2024
84ab8d5
Update test/examples/no_init_lists_examples/pass_no_init_lists3.erl
bormilan Oct 18, 2024
d831670
Update test/examples/no_init_lists_examples/pass_no_init_lists.erl
bormilan Oct 18, 2024
1c18d10
Update test/examples/no_init_lists_examples/pass_no_init_lists2.erl
bormilan Oct 18, 2024
beea14b
Update src/elvis_style.erl
bormilan Oct 18, 2024
2ec9883
Merge branch 'main' into no-init-lists
bormilan Oct 18, 2024
039939e
fix
bormilan Oct 18, 2024
7b85321
remake rule logic
bormilan Oct 18, 2024
379a088
simplify node tuple
bormilan Oct 18, 2024
63aef9b
rename is_rule_behaviour to is_relevant_behaviour
bormilan Oct 18, 2024
02881b8
add no_init_lists_config type
bormilan Oct 18, 2024
5d32c9c
refactor rule logic
bormilan Oct 24, 2024
d1f6cfc
add new test, and case
bormilan Oct 24, 2024
40d7985
fix cases
bormilan Oct 26, 2024
ae45c44
delete unnecessary file
bormilan Oct 27, 2024
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 @@ -62,6 +62,7 @@ identified with `(since ...)` for convenience purposes.
- [State Record and Type](doc_rules/elvis_style/state_record_and_type.md)
- [Used Ignored Variable](doc_rules/elvis_style/used_ignored_variable.md)
- [Variable Naming Convention](doc_rules/elvis_style/variable_naming_convention.md)
- [No Init Lists](doc_rules/elvis_style/no_init_lists.md)
- [Prefer Unquoted Atoms](doc_rules/elvis_text_style/prefer_unquoted_atoms.md)

## `.gitignore` rules
Expand Down
17 changes: 17 additions & 0 deletions doc_rules/elvis_style/no_init_lists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# No Init Lists

Do not use a list as the parameter for the `init/1` callback when implementing `gen_*` behaviours.
It's semantically clearer to use a tuple or a map.
[More info](https://erlangforums.com/t/args-in-gen-init-1/3169/5)

> Works on `.beam` files? Yes!

## Options

- None.

## Example

```erlang
{elvis_style, no_init_lists, #{}}
```
2 changes: 2 additions & 0 deletions src/elvis_rulesets.erl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ rules(erl_files_strict) ->
max_function_length,
max_module_length,
no_call,
no_init_lists,
no_common_caveats_call,
no_macros,
state_record_and_type]]);
Expand All @@ -119,6 +120,7 @@ rules(beam_files) ->
no_debug_call,
no_if_expression,
no_import,
no_init_lists,
no_match_in_condition,
no_nested_try_catch,
no_single_clause_case,
Expand Down
85 changes: 83 additions & 2 deletions src/elvis_style.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
atom_naming_convention/3, no_throw/3, no_dollar_space/3, no_author/3, no_import/3,
no_catch_expressions/3, no_single_clause_case/3, numeric_format/3, behaviour_spelling/3,
always_shortcircuit/3, consistent_generic_type/3, export_used_types/3,
no_match_in_condition/3, param_pattern_matching/3, private_data_types/3, option/3]).
no_match_in_condition/3, param_pattern_matching/3, private_data_types/3, option/3,
no_init_lists/3]).

-export_type([empty_rule_config/0]).
-export_type([ignorable/0]).
Expand All @@ -28,8 +29,10 @@
no_import_config/0, no_catch_expressions_config/0, numeric_format_config/0,
no_single_clause_case_config/0, consistent_variable_casing_config/0,
no_match_in_condition_config/0, behaviour_spelling_config/0,
param_pattern_matching_config/0, private_data_type_config/0]).
param_pattern_matching_config/0, private_data_type_config/0, no_init_lists_config/0]).

-define(NO_INIT_LISTS_MSG,
"Do not use a list as the parameter for the 'init' callback at position ~p.").
-define(INVALID_MACRO_NAME_REGEX_MSG,
"The macro named ~p on line ~p does not respect the format "
"defined by the regular expression '~p'.").
Expand Down Expand Up @@ -147,6 +150,8 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec default(Rule :: atom()) -> DefaultRuleConfig :: term().
default(no_init_lists) ->
#{behaviours => [gen_server, gen_statem]};
elbrujohalcon marked this conversation as resolved.
Show resolved Hide resolved
default(macro_names) ->
#{regex => "^[A-Z](_?[A-Z0-9]+)*$"};
default(operator_spaces) ->
Expand Down Expand Up @@ -1112,6 +1117,82 @@ atom_naming_convention(Config, Target, RuleConfig) ->
AtomNodes = elvis_code:find(fun is_atom_node/1, Root, #{traverse => all, mode => node}),
check_atom_names(Regex, RegexEnclosed, AtomNodes, []).

-type no_init_lists_config() :: #{behaviours => [atom()]}.

-spec no_init_lists(elvis_config:config(), elvis_file:file(), no_init_lists_config()) ->
[elvis_result:item()].
no_init_lists(Config, Target, RuleConfig) ->
Root = get_root(Config, Target, RuleConfig),

InitFuns =
case is_relevant_behaviour(Root, RuleConfig) of
true ->
IsFunction = fun(Node) -> ktn_code:type(Node) == function end,
elbrujohalcon marked this conversation as resolved.
Show resolved Hide resolved
FunctionNodes = elvis_code:find(IsFunction, Root),
ProcessFun =
fun(FunctionNode) ->
Location = ktn_code:attr(location, FunctionNode),
Content = ktn_code:content(FunctionNode),
lists:map(fun(Elem) ->
Attributes = ktn_code:node_attr(pattern, Elem),
{Location, Attributes}
end,
Content)
end,

lists:append(
lists:filtermap(fun(FunctionNode) ->
Name = ktn_code:attr(name, FunctionNode),
case Name of
init ->
{true, ProcessFun(FunctionNode)};
_ ->
false
end
end,
FunctionNodes));
false ->
[]
end,

IsBreakRule =
lists:all(fun({_, Attributes}) ->
length(lists:filter(fun(Elem) -> is_list_node(Elem) end, Attributes)) =:= 1
elbrujohalcon marked this conversation as resolved.
Show resolved Hide resolved
end,
InitFuns),

ResultFun =
fun({Location, _}) ->
Info = [Location],
Msg = ?NO_INIT_LISTS_MSG,
elvis_result:new(item, Msg, Info, Location)
end,

case IsBreakRule of
true ->
lists:map(ResultFun, InitFuns);
false ->
[]
end.

is_relevant_behaviour(Root, RuleConfig) ->
ConfigBehaviors = option(behaviours, RuleConfig, no_init_lists),
IsBehaviour = fun(Node) -> ktn_code:type(Node) == behaviour end,
Behaviours = elvis_code:find(IsBehaviour, Root),
lists:any(fun(Elem) -> Elem =:= true end,
lists:map(fun(BehaviourNode) ->
lists:member(
ktn_code:attr(value, BehaviourNode), ConfigBehaviors)
end,
Behaviours)).

is_list_node(#{type := cons}) ->
true;
is_list_node(#{type := match, content := Content}) ->
lists:any(fun(Elem) -> is_list_node(Elem) end, Content);
is_list_node(_) ->
false.

-spec no_throw(elvis_config:config(), elvis_file:file(), empty_rule_config()) ->
[elvis_result:item()].
no_throw(Config, Target, RuleConfig) ->
Expand Down
3 changes: 3 additions & 0 deletions test/examples/no_init_lists_examples/example_behaviour.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-module(example_behaviour).

-callback example() -> atom().
15 changes: 15 additions & 0 deletions test/examples/no_init_lists_examples/fail_no_init_lists.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-module(fail_no_init_lists).

-behaviour(gen_server).

-export([start_link/1, init/1, handle_cast/2, handle_call/3]).

start_link(AParam) ->
gen_server:start_link(?MODULE, AParam, []).

init([_AParam]) ->
ok.

handle_cast(_, _) -> ok.

handle_call(_, _, _) -> ok.
15 changes: 15 additions & 0 deletions test/examples/no_init_lists_examples/fail_no_init_lists2.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-module(fail_no_init_lists2).

-behaviour(gen_server).

-export([start_link/1, init/1, handle_cast/2, handle_call/3]).

start_link(AParam) ->
gen_server:start_link(?MODULE, AParam, []).

init(_A = [1, 2, 3]) ->
ok.

handle_cast(_, _) -> ok.

handle_call(_, _, _) -> ok.
21 changes: 21 additions & 0 deletions test/examples/no_init_lists_examples/fail_no_init_lists3.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-module(fail_no_init_lists3).

-behaviour(gen_server).

-export([start_link/1, init/1, handle_cast/2, handle_call/3]).

start_link(AParam) ->
gen_server:start_link(?MODULE, AParam, []).

init([_]) ->
ok;

init(_A = [1, 2, 3]) ->
ok;

init([undefined]) ->
ok.

handle_cast(_, _) -> ok.

handle_call(_, _, _) -> ok.
15 changes: 15 additions & 0 deletions test/examples/no_init_lists_examples/fail_no_init_lists4.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-module(fail_no_init_lists4).

-behaviour(gen_server).
-behaviour(example_behaviour).

-export([init/1, handle_cast/2, handle_cast/3, handle_call/3]).
-export([example/0]).

init([]) -> {error, "should not be a list"}.

handle_cast(_, _) -> ok.
handle_cast(_, _, _) -> ok.
handle_call(_, _, _) -> ok.

example() -> ok.
21 changes: 21 additions & 0 deletions test/examples/no_init_lists_examples/pass_no_init_lists.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-module(pass_no_init_lists).

-behaviour(gen_server).

-export([start_link/0, init/1, handle_cast/2, handle_call/3]).

start_link() ->
gen_server:start_link(?MODULE, 1, []).

init(1) ->
ok;

init([undefined]) ->
ok;

init([_]) ->
ok.

handle_cast(_, _) -> ok.

handle_call(_, _, _) -> ok.
18 changes: 18 additions & 0 deletions test/examples/no_init_lists_examples/pass_no_init_lists2.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-module(pass_no_init_lists2).

-behaviour(gen_server).

-export([start_link/0, init/1, init/2, handle_cast/2, handle_call/3]).

start_link() ->
gen_server:start_link(?MODULE, #{a => map}, []).

init(#{}) ->
ok.

init([_], udnefined) ->
ok.

handle_cast(_, _) -> ok.

handle_call(_, _, _) -> ok.
18 changes: 18 additions & 0 deletions test/examples/no_init_lists_examples/pass_no_init_lists3.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-module(pass_no_init_lists3).

-behaviour(gen_server).

-export([start_link/0, init/0, init/1, handle_cast/2, handle_call/3]).

start_link() ->
gen_server:start_link(?MODULE, {1, 2}, []).

init({1, 2}) ->
ok.

init() ->
ok.

handle_cast(_, _) -> ok.

handle_call(_, _, _) -> ok.
8 changes: 8 additions & 0 deletions test/examples/no_init_lists_examples/pass_no_init_lists4.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-module(pass_no_init_lists4).

-export([start_link/1, init/1]).

start_link(AParam) ->
gen_server:start_link(?MODULE, [AParam], []).

init([_AParam]) -> ok.
8 changes: 8 additions & 0 deletions test/examples/no_init_lists_examples/pass_no_init_lists5.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-module(pass_no_init_lists5).

-behaviour(example_behaviour).

-export([init/1, example/0]).

init([]) -> {error, "can be a list"}.
example() -> ok.
27 changes: 26 additions & 1 deletion test/style_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
verify_consistent_generic_type/1, verify_no_types/1, verify_no_specs/1,
verify_export_used_types/1, verify_consistent_variable_casing/1,
verify_no_match_in_condition/1, verify_param_pattern_matching/1,
verify_private_data_types/1, verify_unquoted_atoms/1]).
verify_private_data_types/1, verify_unquoted_atoms/1, verify_no_init_lists/1]).
%% -elvis attribute
-export([verify_elvis_attr_atom_naming_convention/1, verify_elvis_attr_numeric_format/1,
verify_elvis_attr_dont_repeat_yourself/1, verify_elvis_attr_function_naming_convention/1,
Expand Down Expand Up @@ -1521,6 +1521,31 @@ verify_atom_naming_convention(Config) ->
FailPath)
end.

-spec verify_no_init_lists(config()) -> any().
verify_no_init_lists(Config) ->
Ext = proplists:get_value(test_file_ext, Config, "erl"),

ExamplesDir = "no_init_lists_examples/",
FailPath = ExamplesDir ++ "fail_no_init_lists." ++ Ext,
FailPath2 = ExamplesDir ++ "fail_no_init_lists2." ++ Ext,
FailPath3 = ExamplesDir ++ "fail_no_init_lists3." ++ Ext,
% FailPath4 = ExamplesDir ++ "fail_no_init_lists4." ++ Ext,
[_] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, FailPath),
[_] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, FailPath2),
[_, _, _] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, FailPath3),
% [_] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, FailPath4),
PassPath = ExamplesDir ++ "pass_no_init_lists." ++ Ext,
PassPath2 = ExamplesDir ++ "pass_no_init_lists2." ++ Ext,
PassPath3 = ExamplesDir ++ "pass_no_init_lists3." ++ Ext,
PassPath4 = ExamplesDir ++ "pass_no_init_lists4." ++ Ext,
PassPath5 = ExamplesDir ++ "pass_no_init_lists5." ++ Ext,
[] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, PassPath),
[] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, PassPath2),
[] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, PassPath3),
[] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, PassPath4),
[] = elvis_core_apply_rule(Config, elvis_style, no_init_lists, #{}, PassPath5),
ok.

-spec verify_no_throw(config()) -> any().
verify_no_throw(Config) ->
_Group = proplists:get_value(group, Config, erl_files),
Expand Down