From 9a662c65e53b17bf4b8977ac025c99a17de242e8 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Fri, 18 Jul 2014 16:03:02 -0300 Subject: [PATCH] [#14] Modified AST node generation for 'case' and list comprehension. Improved tests. Added utils functions. --- src/elvis_code.erl | 38 ++++++++++++++++++++--- src/elvis_utils.erl | 46 ++++++++++++++++++++++++++-- test/examples/fail_nesting_level.erl | 32 ++++++++++++++++++- test/rules_SUITE.erl | 3 +- 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/src/elvis_code.erl b/src/elvis_code.erl index 1e18f3c..8ede791 100644 --- a/src/elvis_code.erl +++ b/src/elvis_code.erl @@ -2,7 +2,9 @@ -export([ parse_tree/1, - past_nesting_limit/2 + past_nesting_limit/2, + print_node/1, + print_node/2 ]). -export([ @@ -75,6 +77,18 @@ past_nesting_limit(#{content := Content}, past_nesting_limit(_Node, _CurrentLeve, _MaxLevel) -> []. +%% @doc Debugging utility function. +-spec print_node(tree_node()) -> ok. +print_node(Node) -> + print_node(Node, 0). + +-spec print_node(tree_node(), integer()) -> ok. +print_node(Node = #{type := Type}, CurrentLevel) -> + Indentation = lists:duplicate(CurrentLevel * 4, 32), + {Line, _} = elvis_code:attr(location, Node), + lager:info("~s - [~p] ~p : ~p~n", + [Indentation, CurrentLevel, Type, Line]). + %% @private %% @doc Takes a node type and determines its nesting level increment. level_increment(Type) -> @@ -206,9 +220,19 @@ to_map({remote, Location, Module, Function}) -> %% case to_map({'case', Location, Expr, Clauses}) -> + CaseExpr = to_map({case_expr, Location, Expr}), + CaseClauses = to_map({case_clauses, Location, Clauses}), #{type => 'case', attrs => #{location => Location, expression => to_map(Expr)}, + content => [CaseExpr, CaseClauses]}; +to_map({case_expr, Location, Expr}) -> + #{type => case_expr, + attrs => #{location => Location}, + content => [to_map(Expr)]}; +to_map({case_clauses, Location, Clauses}) -> + #{type => case_clauses, + attrs => #{location => Location}, content => to_map(Clauses)}; %% fun @@ -359,15 +383,21 @@ to_map({Type, Location, Key, Value}) when %% List Comprehension to_map({lc, Location, Expr, GeneratorsFilters}) -> + LcExpr = to_map({lc_expr, Location, Expr}), + LcGenerators = to_map(GeneratorsFilters), #{type => lc, - attrs => #{location => Location, - expression => to_map(Expr)}, - content => to_map(GeneratorsFilters)}; + attrs => #{location => Location}, + content => [LcExpr | LcGenerators]}; + to_map({generate, Location, Pattern, Expr}) -> #{type => generate, attrs => #{location => Location, pattern => to_map(Pattern), expression => to_map(Expr)}}; +to_map({lc_expr, Location, Expr}) -> + #{type => lc_expr, + attrs => #{location => Location}, + content => [to_map(Expr)]}; %% Operation diff --git a/src/elvis_utils.erl b/src/elvis_utils.erl index 749ae5a..c2365e6 100644 --- a/src/elvis_utils.erl +++ b/src/elvis_utils.erl @@ -2,14 +2,22 @@ -export([ src/2, + + %% Files find_files/1, find_files/2, + is_erlang_file/1, + filter_files/1, + + %% Rules check_lines/3, + check_lines_with_context/4, + indentation/3, check_nodes/3, + + %% General erlang_halt/1, - to_str/1, - is_erlang_file/1, - filter_files/1 + to_str/1 ]). -export_type([file/0]). @@ -57,6 +65,13 @@ check_lines(Src, Fun, Args) -> Lines = binary:split(Src, <<"\n">>, [global]), check_lines(Lines, Fun, Args, [], 1). +-spec check_lines_with_context(binary(), fun(), [term()], {integer(), integer()}) -> + [elvis_result:item()]. +check_lines_with_context(Src, Fun, Args, Ctx) -> + Lines = binary:split(Src, <<"\n">>, [global]), + LinesContext = context(Lines, Ctx), + check_lines(LinesContext, Fun, Args, [], 1). + %% @private check_lines([], _Fun, _Args, Results, _Num) -> lists:reverse(Results); @@ -68,6 +83,18 @@ check_lines([Line | Lines], Fun, Args, Results, Num) -> check_lines(Lines, Fun, Args, Results, Num + 1) end. +%% @private +context(List, CtxCount) -> + context(List, [], CtxCount, []). + +context([], _Past, _CtxCount, Results) -> + lists:reverse(Results); +context([Current | Future], Past, CtxCount = {PrevCount, NextCount}, Results) -> + Prev = lists:sublist(Past, PrevCount), + Next = lists:sublist(Future, NextCount), + Item = {Current, lists:reverse(Prev), Next}, + context(Future, [Current | Past], CtxCount, [Item | Results]). + %% @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 @@ -114,3 +141,16 @@ is_erlang_file(Path) -> filter_files(Files) -> [File || File = #{path := Path} <- Files, is_erlang_file(Path)]. + +%% @doc Takes a line, a character and a count, returning the indentation level +%% invalid if the number of character is not a multiple of count. +-spec indentation(binary() | string(), char(), integer()) -> + invalid | integer(). +indentation(Line, Char, Count) -> + LineStr = to_str(Line), + Regex = "^" ++ [Char] ++ "*", + {match, [{0, Len} | _]} = re:run(LineStr, Regex), + case Len rem Count of + 0 -> Len div Count; + _ -> invalid + end. diff --git a/test/examples/fail_nesting_level.erl b/test/examples/fail_nesting_level.erl index 102047d..8eff925 100644 --- a/test/examples/fail_nesting_level.erl +++ b/test/examples/fail_nesting_level.erl @@ -124,7 +124,7 @@ exceed_with_receive() -> 3 -> 3 end. -exceed_with_receive_after() -> +dont_exceed_with_receive_after() -> case 1 of 1 -> ok; 2 -> receive @@ -140,3 +140,33 @@ exceed_with_receive_after() -> end; 3 -> 3 end. + +dont_exceed_with_list_compr() -> + case 1 of + 1 -> ok; + 2 -> receive + 1 -> ok; + 2 -> ok; + 3 -> ok + after + 1000 -> + [X || X <- [1, 2, 3]] + end; + 3 -> 3 + end. + +exceed_with_list_compr() -> + case 1 of + 1 -> ok; + 2 -> receive + 1 -> ok; + 2 -> ok; + 3 -> [case X of + 1 -> ok; + _ -> not_ok + end + || X <- [1, 2, 3]]; + 4 -> ok + end; + 3 -> 3 + end. diff --git a/test/rules_SUITE.erl b/test/rules_SUITE.erl index 3216142..2850ebc 100644 --- a/test/rules_SUITE.erl +++ b/test/rules_SUITE.erl @@ -126,4 +126,5 @@ verify_nesting_level(_Config) -> #{line_num := 28}, #{line_num := 43}, #{line_num := 76}, - #{line_num := 118}] = elvis_style:nesting_level(ElvisConfig, File, [3]). + #{line_num := 118}, + #{line_num := 164}] = elvis_style:nesting_level(ElvisConfig, File, [3]).