Skip to content

Commit 9f252f1

Browse files
Add count_docs to max_module_length rule options (#385)
* Add `count_docs` to the `max_module_length` options * Try fix formatter issue * Try fix dialyzer error in OTP < 27 * Rephrase comment Co-authored-by: Brujo Benavides <[email protected]> * Fix test when OTP < 27 * Fix ex_doc error See #385 (comment) --------- Co-authored-by: Brujo Benavides <[email protected]>
1 parent f0deb71 commit 9f252f1

File tree

4 files changed

+118
-18
lines changed

4 files changed

+118
-18
lines changed

doc_rules/elvis_style/max_module_length.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Max Module Length
22

3-
This specifies an upper bound on module file **line** length. Lines that are comments and/or
4-
whitespace can be either included or excluded from the line count.
3+
This specifies an upper bound on module file **line** length. Lines that are comments, and/or
4+
whitespace and/or documentation attributes can be either included or excluded from the line count.
55

66
**Notice**: this rule is not enforced by default. Check the
77
[example `elvis.config` file](../../README.md#configuration) to see how you can
@@ -18,6 +18,8 @@ the files are pre-processed)
1818
- default: `false`
1919
- `count_whitespace :: boolean()`
2020
- default: `false`
21+
- `count_docs :: boolean()`
22+
- default: `false`
2123

2224
## Example
2325

@@ -27,5 +29,6 @@ the files are pre-processed)
2729
{elvis_style, max_module_length, #{ max_length => 500
2830
, count_comments => false
2931
, count_whitespace => false
32+
, count_docs => false
3033
}}
3134
```

src/elvis_style.erl

+49-3
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ default(dont_repeat_yourself) ->
205205
default(max_module_length) ->
206206
#{max_length => 500,
207207
count_comments => false,
208-
count_whitespace => false};
208+
count_whitespace => false,
209+
count_docs => false};
209210
default(max_anonymous_function_arity) ->
210211
#{max_arity => 5};
211212
default(max_function_arity) ->
@@ -300,6 +301,7 @@ default(RuleWithEmptyDefault)
300301
max_length => integer()}.
301302
-type function_naming_convention_config() ::
302303
#{ignore => [ignorable()], regex => string()}.
304+
-type binary_part() :: {Start :: non_neg_integer(), Length :: integer()}.
303305

304306
-spec function_naming_convention(elvis_config:config(),
305307
elvis_file:file(),
@@ -872,12 +874,18 @@ max_module_length(Config, Target, RuleConfig) ->
872874
MaxLength = option(max_length, RuleConfig, max_module_length),
873875
CountComments = option(count_comments, RuleConfig, max_module_length),
874876
CountWhitespace = option(count_whitespace, RuleConfig, max_module_length),
877+
CountDocs = option(count_docs, RuleConfig, max_module_length),
875878

876879
Root = get_root(Config, Target, RuleConfig),
877-
{Src, _} = elvis_file:src(Target),
880+
{Src0, _} = elvis_file:src(Target),
878881

879882
ModuleName = elvis_code:module_name(Root),
880883

884+
DocParts = doc_bin_parts(Src0),
885+
Docs = iolist_to_binary(bin_parts_to_iolist(Src0, DocParts)),
886+
SrcParts = ignore_bin_parts(Src0, DocParts),
887+
Src = iolist_to_binary(bin_parts_to_iolist(Src0, SrcParts)),
888+
881889
FilterFun =
882890
fun(Line) ->
883891
(CountComments orelse not line_is_comment(Line))
@@ -891,7 +899,15 @@ max_module_length(Config, Target, RuleConfig) ->
891899
lists:filter(FilterFun, Ls)
892900
end,
893901

894-
case length(Lines) of
902+
DocLines =
903+
case CountDocs of
904+
true ->
905+
elvis_utils:split_all_lines(Docs, [trim]);
906+
false ->
907+
[]
908+
end,
909+
910+
case length(Lines) + length(DocLines) of
895911
L when L > MaxLength ->
896912
Info = [ModuleName, L, MaxLength],
897913
Msg = ?MAX_MODULE_LENGTH,
@@ -2459,3 +2475,33 @@ get_root(Config, Target, RuleConfig) ->
24592475
_ ->
24602476
Root0
24612477
end.
2478+
2479+
-spec doc_bin_parts(Src) -> [Part]
2480+
when Src :: binary(),
2481+
Part :: binary_part().
2482+
doc_bin_parts(Src) when is_binary(Src) ->
2483+
RE = "(?ms)^-(?:(moduledoc|doc))\\b\\s*(\"*)?.*?\\2\\.(\\r\\n|\\n)",
2484+
case re:run(Src, RE, [global, {capture, first, index}]) of
2485+
{match, Parts} ->
2486+
[Part || [Part] <- Parts];
2487+
nomatch ->
2488+
[]
2489+
end.
2490+
2491+
-spec ignore_bin_parts(Src, [DocPart]) -> [TextPart]
2492+
when Src :: binary(),
2493+
DocPart :: binary_part(),
2494+
TextPart :: binary_part().
2495+
ignore_bin_parts(Src, DocParts) when is_binary(Src), is_list(DocParts) ->
2496+
ignore_bin_parts_1(DocParts, 0, Src).
2497+
2498+
ignore_bin_parts_1([], Prev, Src) ->
2499+
[{Prev, byte_size(Src) - Prev}];
2500+
ignore_bin_parts_1([{Start, Len} | T], Prev, Src) ->
2501+
[{Prev, Start - Prev} | ignore_bin_parts_1(T, Start + Len, Src)].
2502+
2503+
-spec bin_parts_to_iolist(Src, Parts) -> iolist()
2504+
when Src :: binary(),
2505+
Parts :: [binary_part()].
2506+
bin_parts_to_iolist(Src, Parts) when is_binary(Src), is_list(Parts) ->
2507+
[binary_part(Src, Start, Len) || {Start, Len} <- Parts].

test/examples/fail_max_module_length.erl

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
-module(fail_max_module_length).
2-
2+
-if(?OTP_RELEASE >= 27).
3+
-moduledoc false.
4+
-endif.
35
-export([f/1]).
46

57

@@ -11,5 +13,11 @@
1113

1214

1315

14-
%% @doc A function
16+
%% @doc A function.
17+
-if(?OTP_RELEASE >= 27).
18+
-doc """
19+
A function.
20+
""".
21+
-doc(#{since => "1.0"}).
22+
-endif.
1523
f(_) -> ok.

test/style_SUITE.erl

+54-11
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060

6161
-type config() :: [{atom(), term()}].
6262

63+
-if(?OTP_RELEASE < 27).
64+
65+
%% The `verify_max_module_length_docs/3` test only runs on OTP >= 27 because
66+
%% the `-moduledoc` and `-doc` attributes were introduced in OTP-27.
67+
-hank([{unnecessary_function_arguments, [{verify_max_module_length_docs, 3}]}]).
68+
69+
-endif.
70+
6371
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6472
%% Common test
6573
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -990,50 +998,85 @@ verify_max_module_length(Config) ->
990998

991999
PathFail = "fail_max_module_length." ++ Ext,
9921000

993-
CountAllRuleConfig = #{count_comments => true, count_whitespace => true},
1001+
CountAllRuleConfig =
1002+
#{count_comments => true,
1003+
count_whitespace => true,
1004+
count_docs => true},
9941005

995-
ct:comment("Count whitespace and comment lines"),
996-
RuleConfig = CountAllRuleConfig#{max_length => 10},
1006+
ct:comment("Count whitespace, comment, and documentation lines"),
1007+
RuleConfig = CountAllRuleConfig#{max_length => 18},
9971008

9981009
[_] = elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig, PathFail),
9991010

1000-
RuleConfig1 = CountAllRuleConfig#{max_length => 14},
1011+
RuleConfig1 = CountAllRuleConfig#{max_length => 22},
10011012
[_] =
10021013
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig1, PathFail),
10031014

1004-
RuleConfig2 = CountAllRuleConfig#{max_length => 15},
1015+
RuleConfig2 = CountAllRuleConfig#{max_length => 23},
10051016
[] = elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig2, PathFail),
10061017

10071018
ct:comment("Don't count whitespace lines"),
10081019
WhitespaceRuleConfig = CountAllRuleConfig#{count_whitespace => false},
10091020

1010-
RuleConfig3 = WhitespaceRuleConfig#{max_length => 3},
1021+
RuleConfig3 = WhitespaceRuleConfig#{max_length => 12},
10111022
[_] =
10121023
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig3, PathFail),
10131024

1014-
RuleConfig4 = WhitespaceRuleConfig#{max_length => 4},
1025+
RuleConfig4 = WhitespaceRuleConfig#{max_length => 13},
10151026
[_] =
10161027
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig4, PathFail),
10171028

1018-
RuleConfig5 = WhitespaceRuleConfig#{max_length => 5},
1029+
RuleConfig5 = WhitespaceRuleConfig#{max_length => 14},
10191030
[] = elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig5, PathFail),
10201031

10211032
ct:comment("Don't count comment or whitespace lines"),
10221033
NoCountRuleConfig = WhitespaceRuleConfig#{count_comments => false},
10231034

1024-
RuleConfig6 = NoCountRuleConfig#{max_length => 1},
1035+
RuleConfig6 = NoCountRuleConfig#{max_length => 10},
10251036
[_] =
10261037
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig6, PathFail),
10271038

1028-
RuleConfig7 = NoCountRuleConfig#{max_length => 2},
1039+
RuleConfig7 = NoCountRuleConfig#{max_length => 11},
10291040
[_] =
10301041
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig7, PathFail),
10311042

1032-
RuleConfig8 = NoCountRuleConfig#{max_length => 3},
1043+
RuleConfig8 = NoCountRuleConfig#{max_length => 12},
10331044
[] = elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig8, PathFail),
10341045

1046+
ok = verify_max_module_length_docs(PathFail, CountAllRuleConfig, Config),
1047+
10351048
{comment, ""}.
10361049

1050+
%% The `verify_max_module_length_docs/3` test only runs on OTP >= 27 because
1051+
%% the `-moduledoc` and `-doc` attributes were introduced in OTP-27.
1052+
-spec verify_max_module_length_docs(file:filename(), map(), config()) -> ok.
1053+
-if(?OTP_RELEASE >= 27).
1054+
1055+
verify_max_module_length_docs(PathFail, CountAllRuleConfig, Config) ->
1056+
ct:comment("Don't count -moduledoc and -doc attributes"),
1057+
DocsRuleConfig = CountAllRuleConfig#{count_docs => false},
1058+
1059+
RuleConfig9 = DocsRuleConfig#{max_length => 9},
1060+
[_] =
1061+
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig9, PathFail),
1062+
1063+
RuleConfig10 = DocsRuleConfig#{max_length => 17},
1064+
[_] =
1065+
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig10, PathFail),
1066+
1067+
RuleConfig11 = DocsRuleConfig#{max_length => 18},
1068+
[] =
1069+
elvis_core_apply_rule(Config, elvis_style, max_module_length, RuleConfig11, PathFail),
1070+
1071+
ok.
1072+
1073+
-else.
1074+
1075+
verify_max_module_length_docs(_PathFail, _CountAllRuleConfig, _Config) ->
1076+
ok.
1077+
1078+
-endif.
1079+
10371080
-spec verify_max_function_arity(config()) -> any().
10381081
verify_max_function_arity(Config) ->
10391082
Ext = proplists:get_value(test_file_ext, Config, "erl"),

0 commit comments

Comments
 (0)