Packages
elvis_core
5.0.3
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
retired
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.0
3.0.1
3.0.0
2.0.1
2.0.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0
0.7.0
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
Core library for the Erlang style reviewer
Current section
Files
Jump to
Current section
Files
src/elvis_code.erl
-module(elvis_code).
%% General
-export([
find/1,
root/2
]).
%% Specific
-export([
print_node/1,
print_node/2
]).
% These are local debug functions.
-ignore_xref([print_node/1, print_node/2]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-type tree_node() :: ktn_code:tree_node().
-export_type([tree_node/0]).
-type ancestors() :: [tree_node()].
-export_type([ancestors/0]).
-spec find(Options) ->
{nodes, [Node]} | {nodes_and_ancestors, [{Node, ancestors()}]}
when
Options :: #{
% undefined means "all types"
of_types := [ktn_code:tree_node_type()] | undefined,
inside := Node,
% undefined means "don't filter"
filtered_by => fun((Node | {Node, ancestors()}) -> boolean()),
filtered_from => node | node_and_ancestors,
traverse => content | all
},
Node :: tree_node().
find(#{of_types := OfTypes, inside := Inside} = Options) ->
FilteredBy = maps:get(filtered_by, Options, undefined),
FilteredFrom = maps:get(filtered_from, Options, node),
Traverse = maps:get(traverse, Options, content),
case FilteredFrom of
node ->
Pred = build_node_pred(OfTypes, FilteredBy),
{nodes, find_nodes(Pred, Inside, Traverse)};
node_and_ancestors ->
Pred = build_ancestors_pred(OfTypes, FilteredBy),
{nodes_and_ancestors, find_nodes_with_ancestors(Pred, Inside, Traverse)}
end.
build_node_pred(OfTypes, undefined) ->
fun(Node) ->
OfTypes =:= undefined orelse lists:member(ktn_code:type(Node), OfTypes)
end;
build_node_pred(OfTypes, FilteredBy) ->
fun(Node) ->
(OfTypes =:= undefined orelse lists:member(ktn_code:type(Node), OfTypes)) andalso
FilteredBy(Node)
end.
build_ancestors_pred(OfTypes, undefined) ->
fun({Node, _Ancestors}) ->
OfTypes =:= undefined orelse lists:member(ktn_code:type(Node), OfTypes)
end;
build_ancestors_pred(OfTypes, FilteredBy) ->
fun({Node, _Ancestors} = NodeAndAncestors) ->
(OfTypes =:= undefined orelse lists:member(ktn_code:type(Node), OfTypes)) andalso
FilteredBy(NodeAndAncestors)
end.
find_nodes_with_ancestors(Pred, Node, content) ->
find_nodes_content_ancestors(Pred, Node, []);
find_nodes_with_ancestors(Pred, Node, all) ->
{Results, _Seen} = find_nodes_all_ancestors(Pred, Node, [], #{}),
Results.
find_nodes_content_ancestors(Pred, #{content := Content} = Node, Ancestors) ->
Match = [{Node, Ancestors} || Pred({Node, Ancestors})],
Match ++
lists:flatmap(
fun(Child) -> find_nodes_content_ancestors(Pred, Child, [Node | Ancestors]) end,
Content
);
find_nodes_content_ancestors(Pred, Node, Ancestors) when is_map(Node) ->
[{Node, Ancestors} || Pred({Node, Ancestors})];
find_nodes_content_ancestors(_Pred, _Node, _Ancestors) ->
[].
find_nodes_all_ancestors(Pred, Node, Ancestors, Seen) when is_map(Node) ->
case Seen of
#{Node := _} ->
{[], Seen};
#{} ->
Seen1 = Seen#{Node => true},
Match = [{Node, Ancestors} || Pred({Node, Ancestors})],
NewAncestors = [Node | Ancestors],
{ChildResults, Seen2} =
lists:foldl(
fun(Child, {A, S}) ->
{R, S1} = find_nodes_all_ancestors(Pred, Child, NewAncestors, S),
{[R | A], S1}
end,
{[], Seen1},
all_children(Node)
),
{Match ++ lists:append(lists:reverse(ChildResults)), Seen2}
end;
find_nodes_all_ancestors(_Pred, _Node, _Ancestors, Seen) ->
{[], Seen}.
find_nodes(Pred, Node, content) ->
find_nodes_content(Pred, Node);
find_nodes(Pred, Node, all) ->
{Results, _Seen} = find_nodes_all(Pred, Node, #{}),
Results.
find_nodes_content(Pred, #{content := Content} = Node) ->
Match = [Node || Pred(Node)],
Match ++ lists:flatmap(fun(Child) -> find_nodes_content(Pred, Child) end, Content);
find_nodes_content(Pred, Node) when is_map(Node) ->
[Node || Pred(Node)];
find_nodes_content(_Pred, _Node) ->
[].
find_nodes_all(Pred, Node, Seen) when is_map(Node) ->
case Seen of
#{Node := _} ->
{[], Seen};
#{} ->
Seen1 = Seen#{Node => true},
Match = [Node || Pred(Node)],
{ChildResults, Seen2} =
lists:foldl(
fun(Child, {A, S}) ->
{R, S1} = find_nodes_all(Pred, Child, S),
{[R | A], S1}
end,
{[], Seen1},
all_children(Node)
),
{Match ++ lists:append(lists:reverse(ChildResults)), Seen2}
end;
find_nodes_all(_Pred, _Node, Seen) ->
{[], Seen}.
all_children(#{content := Content, node_attrs := NodeAttrs}) ->
Content ++ lists:flatten(maps:values(NodeAttrs));
all_children(#{content := Content}) ->
Content;
all_children(#{node_attrs := NodeAttrs}) ->
lists:flatten(maps:values(NodeAttrs));
all_children(_) ->
[].
-spec root(Rule, ElvisConfig) -> Res when
Rule :: elvis_rule:t(),
ElvisConfig :: elvis_config:t(),
Res :: ktn_code:tree_node().
root(Rule, ElvisConfig) ->
{Root0, File0} = elvis_file:parse_tree(Rule, ElvisConfig),
case elvis_config:ruleset(ElvisConfig) of
Ruleset when Ruleset =:= beam_files; Ruleset =:= beam_files_strict ->
elvis_file:get_abstract_parse_tree(File0);
_ ->
Root0
end.
%% @doc Debugging utility function.
-spec print_node(tree_node()) -> ok.
print_node(Node) ->
print_node(Node, 0).
%% @doc Debugging utility function.
-spec print_node(tree_node(), integer()) -> ok.
print_node(#{type := Type} = Node, CurrentLevel) ->
Type = ktn_code:type(Node),
Indentation = lists:duplicate(CurrentLevel * 4, $\s),
Content = ktn_code:content(Node),
_ = elvis_utils:info("~s - [~p] ~p", [Indentation, CurrentLevel, Type]),
_ = lists:map(fun(Child) -> print_node(Child, CurrentLevel + 1) end, Content),
ok.