diff --git a/.gitignore b/.gitignore index 6c20699..cf5d40c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ ebin *.beam *.plt erl_crash.dump -logs +log*/ # Ignore elvis escript elvis diff --git a/Makefile b/Makefile index e40cab5..fd0506a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PROJECT = elvis -DEPS = lager sync getopt jiffy ibrowse +DEPS = lager sync getopt jiffy ibrowse aleppo TEST_DEPS = meck dep_lager = https://github.com/basho/lager.git 2.0.3 @@ -9,6 +9,7 @@ dep_getopt = https://github.com/jcomellas/getopt v0.8.2 dep_meck = https://github.com/eproxus/meck master dep_jiffy = https://github.com/davisp/jiffy 0.11.3 dep_ibrowse = https://github.com/cmullaparthi/ibrowse v4.1.1 +dep_aleppo = https://github.com/inaka/aleppo master include erlang.mk diff --git a/config/app.config b/config/app.config index b7883c5..cd670a0 100644 --- a/config/app.config +++ b/config/app.config @@ -8,12 +8,13 @@ {elvis_style, no_tabs, []}, {elvis_style, macro_names, []}, {elvis_style, macro_module_names, []}, - {elvis_style, operator_spaces, [{right, ","}, {right, "++"}, {left, "++"}]} + {elvis_style, operator_spaces, [{right, ","}, + {right, "++"}, + {left, "++"}]}, + {elvis_style, nesting_level, [3]} ] } } ] } ]. - -% diff --git a/config/elvis-test.config b/config/elvis-test.config index daaee85..b460d6c 100644 --- a/config/elvis-test.config +++ b/config/elvis-test.config @@ -8,7 +8,10 @@ {elvis_style, no_tabs, []}, {elvis_style, macro_names, []}, {elvis_style, macro_module_names, []}, - {elvis_style, operator_spaces, [{right, ","}, {right, "++"}, {left, "++"}]} + {elvis_style, operator_spaces, [{right, ","}, + {right, "++"}, + {left, "++"}]}, + {elvis_style, nesting_level, [3]} ] } } diff --git a/config/elvis.config b/config/elvis.config index 7632761..cd670a0 100644 --- a/config/elvis.config +++ b/config/elvis.config @@ -8,7 +8,10 @@ {elvis_style, no_tabs, []}, {elvis_style, macro_names, []}, {elvis_style, macro_module_names, []}, - {elvis_style, operator_spaces, [{right, ","}, {right, "++"}, {left, "++"}]} + {elvis_style, operator_spaces, [{right, ","}, + {right, "++"}, + {left, "++"}]}, + {elvis_style, nesting_level, [3]} ] } } diff --git a/config/test.config b/config/test.config index e58e804..50db183 100644 --- a/config/test.config +++ b/config/test.config @@ -8,7 +8,10 @@ {elvis_style, no_tabs, []}, {elvis_style, macro_names, []}, {elvis_style, macro_module_names, []}, - {elvis_style, operator_spaces, [{right, ","}, {right, "++"}, {left, "++"}]} + {elvis_style, operator_spaces, [{right, ","}, + {right, "++"}, + {left, "++"}]}, + {elvis_style, nesting_level, [3]} ] } } diff --git a/src/elvis_code.erl b/src/elvis_code.erl new file mode 100644 index 0000000..1e18f3c --- /dev/null +++ b/src/elvis_code.erl @@ -0,0 +1,393 @@ +-module(elvis_code). + +-export([ + parse_tree/1, + past_nesting_limit/2 + ]). + +-export([ + type/1, + attr/2, + content/1 + ]). + +-type tree_node() :: + #{type => atom(), + attrs => map(), + content => [tree_node()]}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Public API +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec parse_tree(string() | binary()) -> + [{ok | error, erl_parse:abstract_form()}]. +parse_tree(Source) -> + SourceStr = elvis_utils:to_str(Source), + {ok, Tokens, _} = erl_scan:string(SourceStr, {1, 1}, []), + {ok, NewTokens} = aleppo:process_tokens(Tokens), + + Forms = split_when(fun is_dot/1, NewTokens), + ParsedForms = lists:map(fun erl_parse:parse_form/1, Forms), + Children = [to_map(Parsed) || {ok, Parsed} <- ParsedForms], + + #{type => root, + content => Children}. + +%% Getters + +-spec type(tree_node()) -> atom(). +type(#{type := Type}) -> + Type. + +-spec attr(term(), tree_node()) -> term() | undefined. +attr(Key, #{attrs := Attrs}) -> + case maps:is_key(Key, Attrs) of + true -> maps:get(Key, Attrs); + false -> undefined + end. + +-spec content(tree_node()) -> [tree_node()]. +content(#{content := Content}) -> + Content. + +%%% Processing functions + +%% @doc Takes a node and returns all nodes where the nesting limit is exceeded. +-spec past_nesting_limit(tree_node(), integer()) -> + [{tree_node(), integer()}]. +past_nesting_limit(Node, MaxLevel) -> + ResultNodes = past_nesting_limit(Node, 1, MaxLevel), + lists:reverse(ResultNodes). + +past_nesting_limit(Node, CurrentLevel, MaxLevel) when CurrentLevel > MaxLevel -> + [Node]; +past_nesting_limit(#{content := Content}, + CurrentLevel, + MaxLevel) -> + Fun = fun(ChildNode = #{type := Type}) -> + Increment = level_increment(Type), + past_nesting_limit(ChildNode, + Increment + CurrentLevel, + MaxLevel) + end, + lists:flatmap(Fun, Content); +past_nesting_limit(_Node, _CurrentLeve, _MaxLevel) -> + []. + +%% @private +%% @doc Takes a node type and determines its nesting level increment. +level_increment(Type) -> + IncrementOne = [function, + 'case', + 'if', + try_case, + try_catch, + 'fun', + named_fun, + receive_case + ], + case lists:member(Type, IncrementOne) of + true -> 1; + false -> 0 + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Private +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec split_when(fun(), list()) -> list(). +split_when(When, List) -> + split_when(When, List, [[]]). + +split_when(When, [], [[] | Results]) -> + split_when(When, [], Results); +split_when(_When, [], Results) -> + Reversed = lists:map(fun lists:reverse/1, Results), + lists:reverse(Reversed); +split_when(When, [Head | Tail], [Current0 | Rest]) -> + Current = [Head | Current0], + Result = case When(Head) of + true -> + [[], Current | Rest]; + false -> + [Current | Rest] + end, + split_when(When, Tail, Result). + +-spec is_dot(tuple()) -> boolean(). +is_dot({dot, _}) -> true; +is_dot(_) -> false. + +%% @doc Converts a parse tree form the abstract format to a map based repr. +%% TODO: Attributes are not being handled correctly. +-spec to_map(term()) -> tree_node(). +to_map(ListParsed) when is_list(ListParsed) -> + lists:map(fun to_map/1, ListParsed); + +to_map({function, Location, Name, Arity, Clauses}) -> + #{type => function, + attrs => #{location => Location, + name => Name, + arity => Arity}, + content => to_map(Clauses)}; +to_map({function, Name, Arity}) -> + #{type => function, + attrs => #{name => Name, + arity => Arity}}; +to_map({function, Module, Name, Arity}) -> + #{type => function, + attrs => #{module => Module, + name => Name, + arity => Arity}}; + +to_map({clause, Location, Patterns, Guards, Body}) -> + #{type => clause, + attrs => #{location => Location, + patterns => to_map(Patterns), + guards => to_map(Guards)}, + content => to_map(Body)}; + +to_map({match, Location, Left, Right}) -> + #{type => match, + attrs => #{location => Location}, + content => to_map([Left, Right])}; + +to_map({tuple, Location, Elements}) -> + #{type => tuple, + attrs => #{location => Location}, + content => to_map(Elements)}; + +%% Literals + +to_map({Type, Location, Value}) when + Type == atom; + Type == integer; + Type == float; + Type == string; + Type == char -> + #{type => Type, + attrs => #{location => Location, + value => Value}}; + +to_map({bin, Location, Elements}) -> + #{type => binary, + attrs => #{location => Location}, + content => to_map(Elements)}; + +to_map({bin_element, Location, Value, Size, TSL}) -> + #{type => binary_element, + attrs => #{location => Location, + value => to_map(Value), + size => Size, + type_spec_list => TSL}}; + +%% Variables + +to_map({var, Location, Name}) -> + #{type => var, + attrs => #{location => Location, + name => Name}}; + +%% Function call + +to_map({call, Location, Function, Arguments}) -> + #{type => var, + attrs => #{location => Location, + function => to_map(Function), + arguments => to_map(Arguments)}}; + +to_map({remote, Location, Module, Function}) -> + #{type => var, + attrs => #{location => Location, + module => to_map(Module), + function => to_map(Function)}}; + +%% case + +to_map({'case', Location, Expr, Clauses}) -> + #{type => 'case', + attrs => #{location => Location, + expression => to_map(Expr)}, + content => to_map(Clauses)}; + +%% fun + +to_map({'fun', Location, {function, Name, Arity}}) -> + #{type => 'fun', + attrs => #{location => Location, + name => Name, + arity => Arity}}; + +to_map({'fun', Location, {function, Module, Name, Arity}}) -> + #{type => 'fun', + attrs => #{location => Location, + module => Module, + name => Name, + arity => Arity}}; + +to_map({'fun', Location, {clauses, Clauses}}) -> + #{type => 'fun', + attrs => #{location => Location}, + content => to_map(Clauses)}; + +to_map({named_fun, Location, Name, Clauses}) -> + #{type => named_fun, + attrs => #{location => Location, + name => Name}, + content => to_map(Clauses)}; + +%% query - deprecated, implemented for completion. + +to_map({'query', Location, ListCompr}) -> + #{type => 'query', + attrs => #{location => Location}, + content => to_map(ListCompr)}; + +%% try..catch..after + +to_map({'try', Location, Body, [], CatchClauses, AfterBody}) -> + TryBody = to_map(Body), + TryCatch = to_map({try_catch, Location, CatchClauses}), + TryAfter = to_map({try_after, Location, AfterBody}), + + #{type => 'try', + attrs => #{location => Location, + catch_clauses => to_map(CatchClauses), + after_body => to_map(AfterBody)}, + content => TryBody ++ [TryCatch, TryAfter]}; + +%% try..of..catch..after + +to_map({'try', Location, Expr, CaseClauses, CatchClauses, AfterBody}) -> + TryCase = to_map({try_case, Location, Expr, CaseClauses}), + TryCatch = to_map({try_catch, Location, CatchClauses}), + TryAfter = to_map({try_after, Location, AfterBody}), + + #{type => 'try', + attrs => #{location => Location}, + content => [TryCase, TryCatch, TryAfter]}; + +to_map({try_case, Location, Expr, Clauses}) -> + #{type => try_case, + attrs => #{location => Location, + expression => Expr}, + content => to_map(Clauses)}; + +to_map({try_catch, Location, Clauses}) -> + #{type => try_catch, + attrs => #{location => Location}, + content => to_map(Clauses)}; + +to_map({try_after, Location, AfterBody}) -> + #{type => try_after, + attrs => #{location => Location}, + content => to_map(AfterBody)}; + +%% if + +to_map({'if', Location, IfClauses}) -> + #{type => 'if', + attrs => #{location => Location}, + content => to_map(IfClauses)}; + +%% catch + +to_map({'catch', Location, Expr}) -> + #{type => 'catch', + attrs => #{location => Location}, + content => [to_map(Expr)]}; + +%% receive + +to_map({'receive', Location, Clauses}) -> + RecClauses = to_map({receive_case, Location, Clauses}), + #{type => 'receive', + attrs => #{location => Location}, + content => [RecClauses]}; + +to_map({'receive', Location, Clauses, AfterExpr, AfterBody}) -> + RecClauses = to_map({receive_case, Location, Clauses}), + RecAfter = to_map({receive_after, Location, AfterExpr, AfterBody}), + #{type => 'receive', + attrs => #{location => Location}, + content => [RecClauses, RecAfter]}; + +to_map({receive_case, Location, Clauses}) -> + #{type => receive_case, + attrs => #{location => Location}, + content => to_map(Clauses)}; + +to_map({receive_after, Location, Expr, Body}) -> + #{type => receive_after, + attrs => #{location => Location, + expression => to_map(Expr)}, + content => to_map(Body)}; + +%% List + +to_map({nil, Location}) -> + #{type => nil, + attrs => #{location => Location}}; + +to_map({cons, Location, Head, Tail}) -> + #{type => cons, + attrs => #{location => Location, + head => to_map(Head), + tail => to_map(Tail)}}; + +%% Map + +to_map({map, Location, Pairs}) -> + #{type => map, + attrs => #{location => Location}, + content => to_map(Pairs)}; +to_map({map, Location, Var, Pairs}) -> + #{type => map, + attrs => #{location => Location, + var => to_map(Var)}, + content => to_map(Pairs)}; + +to_map({Type, Location, Key, Value}) when + map_field_exact == Type; + map_field_assoc == Type -> + #{type => map_field_exact, + attrs => #{location => Location, + key => to_map(Key), + value => to_map(Value)}}; + +%% List Comprehension + +to_map({lc, Location, Expr, GeneratorsFilters}) -> + #{type => lc, + attrs => #{location => Location, + expression => to_map(Expr)}, + content => to_map(GeneratorsFilters)}; +to_map({generate, Location, Pattern, Expr}) -> + #{type => generate, + attrs => #{location => Location, + pattern => to_map(Pattern), + expression => to_map(Expr)}}; + +%% Operation + +to_map({op, Location, Operation, Left, Right}) -> + #{type => op, + attrs => #{location => Location, + operation => Operation}, + content => to_map([Left, Right])}; + +%% Attributes + + +to_map({attribute, Location, Type, Value}) -> + #{type => Type, + attrs => #{location => Location, + value => Value}}; + +%% Unhandled forms + +to_map(Parsed) when is_tuple(Parsed) -> + throw({unhandled_abstract_form, Parsed}); +to_map(Parsed) -> + throw({unexpected_abstract_form, Parsed}). diff --git a/src/elvis_style.erl b/src/elvis_style.erl index a1776cb..247e863 100644 --- a/src/elvis_style.erl +++ b/src/elvis_style.erl @@ -5,7 +5,8 @@ no_tabs/3, macro_names/3, macro_module_names/3, - operator_spaces/3 + operator_spaces/3, + nesting_level/3 ]). -define(LINE_LENGTH_MSG, "Line ~p is too long: ~p."). @@ -17,6 +18,9 @@ -define(MACRO_AS_FUNCTION_NAME_MSG, "Don't use macros (like ~s on line ~p) as function names."). -define(OPERATOR_SPACE_MSG, "Missing space ~s ~p on line ~p"). +-define(NESTING_LEVEL_MSG, + "The expression on line ~p and column ~p is nested " + "beyond the maximum level of ~p."). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Rules @@ -24,36 +28,45 @@ %% @doc Target can be either a filename or the %% name of a module. --spec line_length(elvis_config:config(), map(), [term()]) -> +-spec line_length(elvis_config:config(), elvis_utils:file(), [term()]) -> [elvis_result:item()]. line_length(Config, Target, [Limit]) -> {ok, Src} = elvis_utils:src(Config, Target), elvis_utils:check_lines(Src, fun check_line_length/3, [Limit]). --spec no_tabs(elvis_config:config(), map(), [term()]) -> +-spec no_tabs(elvis_config:config(), elvis_utils:file(), [term()]) -> [elvis_result:item()]. no_tabs(Config, Target, []) -> {ok, Src} = elvis_utils:src(Config, Target), elvis_utils:check_lines(Src, fun check_no_tabs/3, []). --spec macro_names(elvis_config:config(), map(), [term()]) -> +-spec macro_names(elvis_config:config(), elvis_utils:file(), [term()]) -> [elvis_result:item()]. macro_names(Config, Target, []) -> {ok, Src} = elvis_utils:src(Config, Target), elvis_utils:check_lines(Src, fun check_macro_names/3, []). --spec macro_module_names(elvis_config:config(), map(), []) -> +-spec macro_module_names(elvis_config:config(), elvis_utils:file(), []) -> [elvis_result:item_result()]. macro_module_names(Config, Target, []) -> {ok, Src} = elvis_utils:src(Config, Target), elvis_utils:check_lines(Src, fun check_macro_module_names/3, []). --spec operator_spaces(elvis_config:config(), map(), [{right|left, string()}]) -> +-spec operator_spaces(elvis_config:config(), + elvis_utils:file(), + [{right|left, string()}]) -> [elvis_result:item_result()]. operator_spaces(Config, Target, Rules) -> {ok, Src} = elvis_utils:src(Config, Target), elvis_utils:check_lines(Src, fun check_operator_spaces/3, Rules). +-spec nesting_level(elvis_config:config(), elvis_utils:file(), [integer()]) -> + [elvis_result:item_result()]. +nesting_level(Config, Target, [Level]) -> + {ok, Src} = elvis_utils:src(Config, Target), + Root = elvis_code:parse_tree(Src), + elvis_utils:check_nodes(Root, fun check_nesting_level/2, [Level]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Private %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -159,3 +172,20 @@ check_operator_spaces_rule(Line, Num, {left, Operator}) -> Result = elvis_result:new(item, Msg, Info, Num), {ok, Result} end. + +-spec check_nesting_level(elvis_code:tree_node(), [integer()]) -> + [elvis_result:item_result()]. +check_nesting_level(ParentNode, [MaxLevel]) -> + case elvis_code:past_nesting_limit(ParentNode, MaxLevel) of + [] -> []; + NestedNodes -> + Msg = ?NESTING_LEVEL_MSG, + + Fun = fun(Node) -> + {Line, Col} = elvis_code:attr(location, Node), + Info = [Line, Col, MaxLevel], + elvis_result:new(item, Msg, Info, Line) + end, + + lists:map(Fun, NestedNodes) + end. diff --git a/src/elvis_utils.erl b/src/elvis_utils.erl index 7f7cf4d..749ae5a 100644 --- a/src/elvis_utils.erl +++ b/src/elvis_utils.erl @@ -5,6 +5,7 @@ find_files/1, find_files/2, check_lines/3, + check_nodes/3, erlang_halt/1, to_str/1, is_erlang_file/1, @@ -67,6 +68,28 @@ check_lines([Line | Lines], Fun, Args, Results, Num) -> check_lines(Lines, Fun, Args, Results, Num + 1) end. +%% @doc Takes a binary that holds source code and applies +%% Fun to each line. Fun takes 3 arguments (the line +%% as a binary, the line number and the supplied Args) and +%% returns 'no_result' or {'ok', Result}. +-spec check_nodes(elvis_code:tree_node(), fun(), [term()]) -> + [elvis_result:item()]. +check_nodes(RootNode, Fun, Args) -> + ChildNodes = elvis_code:content(RootNode), + check_nodes(ChildNodes, Fun, Args, []). + +%% @private +check_nodes([], _Fun, _Args, Results) -> + FlatResults = lists:flatten(Results), + lists:reverse(FlatResults); +check_nodes([Node | Nodes], Fun, Args, Results) -> + case Fun(Node, Args) of + [] -> + check_nodes(Nodes, Fun, Args, Results); + Result -> + check_nodes(Nodes, Fun, Args, [Result | Results]) + end. + %% @doc This is defined so tht it an be mocked for tests. -spec erlang_halt(integer()) -> any(). erlang_halt(Code) -> diff --git a/test/elvis.coverspec b/test/elvis.coverspec index e4c043c..6d2b2e7 100644 --- a/test/elvis.coverspec +++ b/test/elvis.coverspec @@ -9,6 +9,7 @@ elvis_config, elvis_github, elvis_git, - elvis_webhook + elvis_webhook, + elvis_code ] }. diff --git a/test/examples/fail_nesting_level.erl b/test/examples/fail_nesting_level.erl new file mode 100644 index 0000000..102047d --- /dev/null +++ b/test/examples/fail_nesting_level.erl @@ -0,0 +1,142 @@ +-module(fail_nesting_level). + +%% Used so that the line positions don't change for tests. +-compile([export_all]). + +exceed_with_four_levels() -> + case 1 of + 1 -> case 2 of + 2 -> case 3 of + 3 -> fourth + end + end + end, + case 1 of + 1 -> case 2 of + 2 -> case 3 of + 3 -> fourth + end + end + end. + +exceed_at_diff_branches() -> + case 1 of + 1 -> ok; + 2 -> case 2 of + 1 -> ok; + 2 -> ok; + 3 -> if + true -> true; + false -> false + end; + 4 -> ok + end; + 3 -> 3 + end. + +exceed_with_try_of() -> + case 1 of + 1 -> ok; + 2 -> try 2 of + 1 -> ok; + 2 -> ok; + 3 -> if + true -> true; + false -> false + end; + 4 -> ok + catch + _:_ -> ok + end; + 3 -> 3 + end. + +dont_exceed_with_try() -> + case 1 of + 1 -> ok; + 2 -> try + 2, + if + true -> true; + false -> false + end + catch + _:_ -> ok + end; + 3 -> 3 + end. + +exceed_with_try_catch() -> + case 1 of + 1 -> ok; + 2 -> try + 2 + catch + _:_ -> + if + true -> true; + false -> false + end + end; + 3 -> 3 + end. + +dont_exceed_with_try_after() -> + case 1 of + 1 -> ok; + 2 -> try + 2 + catch + _:_ -> ok + after + if + true -> true; + false -> false + end + end; + 3 -> 3 + end. + +dont_exceed_with_catch() -> + case 1 of + 1 -> ok; + 2 -> catch + if + true -> true; + false -> false + end; + 3 -> 3 + end. + +exceed_with_receive() -> + case 1 of + 1 -> ok; + 2 -> receive + 1 -> ok; + 2 -> ok; + 3 -> + if + bla -> true; + false -> false + end; + 4 -> ok + end; + 3 -> 3 + end. + +exceed_with_receive_after() -> + case 1 of + 1 -> ok; + 2 -> receive + 1 -> ok; + 2 -> ok; + 3 -> ok + after + 1000 -> + if + true -> true; + false -> false + end + end; + 3 -> 3 + end. diff --git a/test/rules_SUITE.erl b/test/rules_SUITE.erl index b2e05af..3216142 100644 --- a/test/rules_SUITE.erl +++ b/test/rules_SUITE.erl @@ -11,7 +11,8 @@ verify_no_tabs_rule/1, verify_macro_names_rule/1, verify_macro_module_names/1, - verify_operator_spaces/1 + verify_operator_spaces/1, + verify_nesting_level/1 ]). -define(EXCLUDED_FUNS, @@ -85,7 +86,7 @@ verify_macro_names_rule(_Config) -> verify_macro_module_names(_Config) -> ElvisConfig = elvis_config:default(), #{src_dirs := SrcDirs} = ElvisConfig, - + File = "fail_macro_module_names.erl", {ok, Path} = elvis_test_utils:find_file(SrcDirs, File), @@ -95,16 +96,34 @@ verify_macro_module_names(_Config) -> verify_operator_spaces(_Config) -> ElvisConfig = elvis_config:default(), #{src_dirs := SrcDirs} = ElvisConfig, - + File = "fail_operator_spaces.erl", {ok, Path} = elvis_test_utils:find_file(SrcDirs, File), [] = elvis_style:operator_spaces(ElvisConfig, Path, []), - + [_, _, _] = elvis_style:operator_spaces(ElvisConfig, Path, [{right, [","]}]), - + AppendOptions = [{right, ["++"]}, {left, ["++"]}], [_] = elvis_style:operator_spaces(ElvisConfig, Path, AppendOptions), AllOptions = [{right, ","}, {right, "++"}, {left, ["++"]}], [_, _, _, _] = elvis_style:operator_spaces(ElvisConfig, Path, AllOptions). + +-spec verify_nesting_level(config()) -> any(). +verify_nesting_level(_Config) -> + ElvisConfig = elvis_config:default(), + + #{src_dirs := SrcDirs} = ElvisConfig, + + lager:info("~p", [SrcDirs]), + + Path = "fail_nesting_level.erl", + {ok, File} = elvis_test_utils:find_file(SrcDirs, Path), + + [#{line_num := 9}, + #{line_num := 16}, + #{line_num := 28}, + #{line_num := 43}, + #{line_num := 76}, + #{line_num := 118}] = elvis_style:nesting_level(ElvisConfig, File, [3]).