Current section

Files

Jump to
elvis_core src elvis_result.erl
Raw

src/elvis_result.erl

-module(elvis_result).
-compile({no_auto_import, [error/2]}).
%% API
-export([new_item/1, new_item/2, new_item/3, new/3, status/1, clean/1, print_results/1]).
-export([
get_path/1,
get_rules/1,
get_items/1,
get_message/1,
get_info/1,
get_line_num/1
]).
%% Types
-export_type([item/0, rule/0, file/0, elvis_error/0, elvis_warn/0]).
% API exports, not consumed locally.
-ignore_xref([get_path/1, get_rules/1, get_items/1, get_message/1, get_info/1, get_line_num/1]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Records
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-opaque item() ::
#{
message => string(),
info => [term()],
line_num => -1 | non_neg_integer(),
column_num => -1 | non_neg_integer()
}.
-opaque rule() ::
#{
ns => atom(),
name => atom(),
items => [item()]
}.
-opaque file() :: #{file => string(), rules => [rule()]}.
-opaque elvis_error() :: #{error_msg => string(), info => list()}.
-opaque elvis_warn() :: #{warn_msg => string(), info => list()}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% New
-spec new_item(Format :: string()) -> item().
new_item(Format) ->
new_item(Format, []).
-spec new_item(Format :: string(), Data :: [term()]) -> item().
new_item(Format, Data) ->
new_item(Format, Data, #{}).
-spec new_item(Format :: string(), Data :: [term()], Attrs) -> item() when
Attrs :: #{
node => ktn_code:tree_node(),
zipper => zipper:zipper(ktn_code:tree_node()),
line => -1 | non_neg_integer(),
column => -1 | non_neg_integer(),
limit => -1 | non_neg_integer()
}.
new_item(Format, Data, Attrs) ->
Attrs1 = extend_attrs_with_line_and_column(Attrs),
Line = maps:get(line, Attrs1, -1),
Limit = maps:get(limit, Attrs1, -1),
Column = maps:get(column, Attrs1, -1),
new(item, Format, Data, {Line, Column}, Limit).
extend_attrs_with_line_and_column(#{node := Node} = Attrs) ->
{Line, Column} = ktn_code:attr(location, Node),
Attrs#{line => Line, column => Column};
extend_attrs_with_line_and_column(#{zipper := Zipper} = Attrs) ->
extend_attrs_with_line_and_column(Attrs#{node => zipper:node(Zipper)});
extend_attrs_with_line_and_column(Attrs) ->
Attrs.
-spec new
(item, string(), [term()]) -> item();
(rule, elvis_rule:t(), [item()]) -> rule();
(file, elvis_file:t(), [elvis_error() | rule()]) -> file();
(error, string(), string()) -> elvis_error();
(warn, string(), string()) -> elvis_warn().
% new(item, ...) is kept for backward compatibility, but discouraged
new(item, Msg, Info) ->
new_item(Msg, Info);
new(rule, Rule, Results) ->
#{
ns => elvis_rule:ns(Rule),
name => elvis_rule:name(Rule),
items => Results
};
new(file, #{path := Path}, Rules) ->
#{file => Path, rules => Rules};
new(error, Msg, Info) ->
#{error_msg => Msg, info => Info};
new(warn, Msg, Info) ->
#{warn_msg => Msg, info => Info}.
new(item, Msg0, Info, {Line, Column}, Limit) ->
Prefix0 =
case Line of
-1 ->
"";
_ ->
"At line " ++ integer_to_list(Line) ++ ", "
end,
Prefix1 =
case Column of
-1 ->
Prefix0;
_ when Line =:= -1 ->
"";
_ ->
Prefix0 ++ "column " ++ integer_to_list(Column) ++ ", "
end,
LimitSuffix =
case Limit of
-1 ->
"";
_ ->
" (limit: " ++ integer_to_list(Limit) ++ ")"
end,
#{
message => Prefix1 ++ Msg0 ++ LimitSuffix ++ ".",
info => Info,
line_num => Line,
column_num => Column
}.
%% Getters
-spec get_path(file()) -> string().
get_path(#{file := File}) ->
File.
-spec get_rules(file()) -> [rule()].
get_rules(#{rules := Rules}) ->
Rules.
-spec get_items(rule()) -> [item()].
get_items(#{items := Items}) ->
Items;
get_items(_) ->
[].
-spec get_message(item()) -> string().
get_message(#{message := Message}) ->
Message.
-spec get_info(item()) -> string().
get_info(#{info := Info}) ->
Info.
-spec get_line_num(item()) -> integer().
get_line_num(#{line_num := LineNum}) ->
LineNum.
%% Print
-spec print_results(file() | [elvis_warn()]) -> ok.
print_results(Results) ->
Format = elvis_config:output_format(),
print(Format, Results).
-spec print(plain | colors | parsable, [file() | elvis_warn()] | file()) -> ok.
print(_, []) ->
ok;
print(Format, [Result | Results]) ->
print(Format, Result),
print(Format, Results);
%% File
print(Format, #{file := Path, rules := Rules}) ->
case Format of
parsable ->
ok;
_ ->
Verbose = elvis_config:verbose(),
case status(Rules) of
ok when Verbose ->
elvis_utils:notice("# ~s [{{green-bold}}OK{{white-bold}}]", [Path]);
ok ->
ok;
fail ->
elvis_utils:notice("# ~s [{{red-bold}}FAIL{{white-bold}}]", [Path])
end
end,
print_rules(Format, Path, Rules);
print(_, Error) ->
print_error(Error).
print_rules(_Format, _File, []) ->
ok;
print_rules(Format, File, [#{items := []} | Items]) ->
print_rules(Format, File, Items);
print_rules(
Format,
File,
[
#{
ns := Scope,
items := Items,
name := Name
}
| EItems
]
) ->
case Format of
parsable ->
ok;
_ ->
elvis_utils:notice(
" - ~p "
"(https://github.com/inaka/elvis_core/tree/main/doc_rules/~p/~p.md)",
[Name, Scope, Name]
)
end,
print_item(Format, File, Name, Items),
print_rules(Format, File, EItems);
print_rules(Format, File, [Error | Items]) ->
print_error(Error),
print_rules(Format, File, Items).
%% Item
print_item(
Format,
File,
Name,
[
#{
message := Msg,
line_num := Ln,
info := Info
}
| Items
]
) ->
case Format of
parsable ->
FMsg = io_lib:format(Msg, Info),
io:format("~s:~p:~p:~s~n", [File, Ln, Name, FMsg]);
_ ->
elvis_utils:notice(" - " ++ Msg, Info)
end,
print_item(Format, File, Name, Items);
print_item(Format, File, Name, [Error | Items]) ->
print_error(Error),
print_item(Format, File, Name, Items);
print_item(_Format, _File, _Name, []) ->
ok.
print_error(#{error_msg := Msg, info := Info}) ->
elvis_utils:error(Msg, Info);
print_error(#{warn_msg := Msg, info := Info}) ->
elvis_utils:warn(Msg, Info).
-spec status([file() | rule()]) -> ok | fail.
status([]) ->
ok;
status([#{rules := Rules} | Files]) ->
case status(Rules) of
fail ->
fail;
ok ->
status(Files)
end;
status([#{items := []} | Rules]) ->
status(Rules);
status(_Rules) ->
fail.
%% @doc Removes files that don't have any failures.
-spec clean([file() | rule()]) -> [file() | rule()].
clean(Files) ->
clean(Files, []).
-spec clean([file() | rule()], [file() | rule()]) -> [file() | rule()].
clean([], Result) ->
lists:reverse(Result);
clean([#{rules := []} | Files], Result) ->
clean(Files, Result);
clean([File = #{rules := Rules} | Files], Result) ->
CleanRules = clean(Rules),
NewFile = File#{rules => CleanRules},
clean(Files, [NewFile | Result]);
clean([#{items := []} | Rules], Result) ->
clean(Rules, Result);
clean([Rule | Rules], Result) ->
clean(Rules, [Rule | Result]).