Packages
elvis_core
4.2.1
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_text_style.erl
-module(elvis_text_style).
-behaviour(elvis_rule).
-export([default/1]).
-export([
line_length/2,
no_tabs/2,
no_trailing_whitespace/2,
no_redundant_blank_lines/2
]).
% The whole file is considered to have either callback functions or rules.
-ignore_xref(elvis_text_style).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Default values
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec default(RuleName :: atom()) -> elvis_rule:def().
default(line_length) ->
elvis_rule:defmap(#{
limit => 100,
skip_comments => false,
no_whitespace_after_limit => true
});
default(no_trailing_whitespace) ->
elvis_rule:defmap(#{
ignore_empty_lines => false
});
default(no_redundant_blank_lines) ->
elvis_rule:defmap(#{
max_lines => 1
});
default(_RuleName) ->
elvis_rule:defmap(#{}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% @doc File can be either a filename or the
%% name of a module.
-spec line_length(elvis_rule:t(), elvis_config:t()) -> [elvis_result:item()].
line_length(Rule, _ElvisConfig) ->
Limit = elvis_rule:option(limit, Rule),
SkipComments = elvis_rule:option(skip_comments, Rule),
NoWhitespace = elvis_rule:option(no_whitespace_after_limit, Rule),
{Src, File} = elvis_file:src(elvis_rule:file(Rule)),
Encoding = elvis_file:encoding(File),
Args = [Limit, SkipComments, Encoding, NoWhitespace],
check_lines(Src, fun check_line_length/3, Args).
-spec no_tabs(elvis_rule:t(), elvis_config:t()) -> [elvis_result:item()].
no_tabs(Rule, _ElvisConfig) ->
{Src, _} = elvis_file:src(elvis_rule:file(Rule)),
check_lines(Src, fun check_no_tabs/2, []).
-spec no_trailing_whitespace(elvis_rule:t(), elvis_config:t()) -> [elvis_result:item()].
no_trailing_whitespace(Rule, _ElvisConfig) ->
{Src, _} = elvis_file:src(elvis_rule:file(Rule)),
IgnoreEmptyLines = elvis_rule:option(ignore_empty_lines, Rule),
check_lines(
Src,
fun(Src1, Fun, _Args) ->
check_no_trailing_whitespace(Src1, Fun, IgnoreEmptyLines)
end,
elvis_rule:def(Rule)
).
-spec no_redundant_blank_lines(elvis_rule:t(), elvis_config:t()) -> [elvis_result:item()].
no_redundant_blank_lines(Rule, _ElvisConfig) ->
MaxLines = elvis_rule:option(max_lines, Rule) + 1,
{Src, _} = elvis_file:src(elvis_rule:file(Rule)),
Lines = elvis_utils:split_all_lines(Src, [trim]),
Result = redundant_blank_lines(Lines, {1, []}),
ResultFun =
fun
({Line, BlankLinesLength}) when BlankLinesLength >= MaxLines ->
{true,
elvis_result:new_item(
"there are too many (~p) blank lines; prefer respecting the configured "
"limit",
[BlankLinesLength],
#{line => Line, limit => BlankLinesLength}
)};
(_) ->
false
end,
lists:filtermap(ResultFun, Result).
redundant_blank_lines([], {_, Result}) ->
Result;
redundant_blank_lines(Lines, {CurrentLineNum, ResultList}) ->
BlankLines = lists:takewhile(fun(X) -> X =:= <<>> end, Lines),
BlankElements = length(BlankLines),
Index =
case BlankElements of
0 ->
1;
Num ->
Num
end,
NextLineNum = CurrentLineNum + Index,
case BlankElements of
0 ->
redundant_blank_lines(lists:nthtail(Index, Lines), {NextLineNum, ResultList});
_ ->
redundant_blank_lines(
lists:nthtail(Index, Lines),
{NextLineNum, [{CurrentLineNum, BlankElements} | ResultList]}
)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Line Length
-spec line_is_comment(binary()) -> boolean().
line_is_comment(Line) ->
re:run(Line, "^[ \t]*%") =/= nomatch.
-spec remove_comment(binary()) -> binary().
remove_comment(Line) ->
case re:run(Line, "([^%]+)", [{capture, first, binary}]) of
nomatch ->
Line;
{match, [Without]} ->
Without
end.
-spec check_line_length(binary(), integer(), [term()]) ->
no_result | {ok, elvis_result:item()}.
check_line_length(Line, Num, [Limit, whole_line, Encoding, NoWhitespace]) ->
case line_is_comment(Line) of
false ->
check_line_length(Line, Num, [Limit, Encoding, NoWhitespace]);
true ->
no_result
end;
check_line_length(Line, Num, [Limit, any, Encoding, NoWhitespace]) ->
LineWithoutComment = remove_comment(Line),
check_line_length(LineWithoutComment, Num, [Limit, Encoding, NoWhitespace]);
check_line_length(Line, Num, [Limit, _, Encoding, NoWhitespace]) ->
check_line_length(Line, Num, [Limit, Encoding, NoWhitespace]);
check_line_length(Line0, Num, [Limit, Encoding, NoWhitespace]) ->
Line = unicode:characters_to_binary(Line0, Encoding),
case string:length(Line) of
Len when Len =< Limit ->
no_result;
Len when NoWhitespace ->
{ok, line_length_res(Num, Len, Limit)};
Len ->
case binary:match(Line, <<"\s">>, [{scope, {Limit, Len - Limit}}]) of
{_, _} ->
{ok, line_length_res(Num, Len, Limit)};
nomatch ->
no_result
end
end.
line_length_res(Num, Len, Limit) ->
elvis_result:new_item(
"there are too many (~p) characters; prefer respecting the configured limit",
[Len],
#{line => Num, limit => Limit}
).
%% No Tabs
-spec check_no_tabs(binary(), integer()) -> no_result | {ok, elvis_result:item()}.
check_no_tabs(Line, Num) ->
case binary:match(Line, <<"\t">>) of
nomatch ->
no_result;
{Index, _} ->
{ok,
elvis_result:new_item(
"an unexpected tab character was found; prefer spaces",
[],
#{line => Num, column => Index}
)}
end.
%% No Trailing Whitespace
-spec check_no_trailing_whitespace(binary(), integer(), boolean()) ->
no_result | {ok, elvis_result:item()}.
check_no_trailing_whitespace(Line, Num, IgnoreEmptyLines) ->
Regex =
case IgnoreEmptyLines of
%% Lookbehind assertion: https://erlang.org/doc/man/re.html#sect17
true ->
"(?<=\\S)\\s+$";
false ->
"\\s+$"
end,
case re:run(Line, Regex) of
nomatch ->
no_result;
{match, _} ->
{ok,
elvis_result:new_item(
"unexpected trailing whitespace was found",
[],
#{line => Num}
)}
end.
%% @doc Takes a binary that holds source code and applies
%% Fun to each line. Fun takes 2 or 3 arguments (the line
%% as a binary, the line number and the optional supplied Args) and
%% returns 'no_result' or {'ok', Result}.
-spec check_lines(binary(), fun(), term()) -> [elvis_result:item()].
check_lines(Src, Fun, Args) ->
Lines = elvis_utils:split_all_lines(Src),
check_lines(Lines, Fun, Args, [], 1).
check_lines([], _Fun, _Args, Results, _Num) ->
lists:flatten(
lists:reverse(Results)
);
check_lines([Line | Lines], Fun, Args, Results, Num) ->
FunRes =
case is_function(Fun, 3) of
true ->
Fun(Line, Num, Args);
false ->
Fun(Line, Num)
end,
case FunRes of
{ok, Result} ->
check_lines(Lines, Fun, Args, [Result | Results], Num + 1);
no_result ->
check_lines(Lines, Fun, Args, Results, Num + 1)
end.