Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
13c176d
Simplify it for elvis_text_style (also drop single-use macros)
paulo-ferraz-oliveira Jun 13, 2025
38f96ec
Separate elvis_text_style to its own SUITE
paulo-ferraz-oliveira Jun 13, 2025
b0d81e8
Simplify it for elvis_project (also drop single-use macros)
paulo-ferraz-oliveira Jun 13, 2025
457f149
Simplify it for elvis_gitignore (also drop single-use macros)
paulo-ferraz-oliveira Jun 13, 2025
5c43612
Make result builder smarter
paulo-ferraz-oliveira Jun 13, 2025
6d6f61f
Remove extraneous end of message .
paulo-ferraz-oliveira Jun 14, 2025
db1a1b2
Fix test
paulo-ferraz-oliveira Jun 14, 2025
c980eeb
Aim toward a macroless elvis_core
paulo-ferraz-oliveira Jun 14, 2025
d2e6aab
Remove single-use macro
paulo-ferraz-oliveira Jun 14, 2025
5014a1c
Prefer argument to parameter
paulo-ferraz-oliveira Jun 14, 2025
422a2e6
Care less about order when creating elements
paulo-ferraz-oliveira Jun 14, 2025
3ad9df6
Simplify it for elvis_style (also drop single-use macros)
paulo-ferraz-oliveira Jun 15, 2025
744b8c8
Propose different example (newer syntax)
paulo-ferraz-oliveira Jun 15, 2025
e8972eb
Signal it better
paulo-ferraz-oliveira Jun 15, 2025
4d77546
Expand elvis_result to make for an easier consumption
paulo-ferraz-oliveira Jun 15, 2025
07ea108
Simplify it, where possible
paulo-ferraz-oliveira Jun 15, 2025
e6cf143
Give messages a short review
paulo-ferraz-oliveira Jun 15, 2025
59aba68
Improve documentation and rule output consistency
paulo-ferraz-oliveira Jun 15, 2025
3d9ceaf
Fix it for accumulation
paulo-ferraz-oliveira Jun 15, 2025
45dc4e9
Make the result readable
paulo-ferraz-oliveira Jun 15, 2025
ff89ced
Move generic test code to test utilities' module
paulo-ferraz-oliveira Jun 15, 2025
c58616e
Give messages a longer review
paulo-ferraz-oliveira Jun 15, 2025
7abfc1e
Complete review of text output
paulo-ferraz-oliveira Jun 15, 2025
8be6add
Replace flatmap with filtermap for a bit more readability
paulo-ferraz-oliveira Jun 15, 2025
2a7e6be
Replace some simple iterations with smaller forms
paulo-ferraz-oliveira Jun 15, 2025
adbaf1a
Fix statement vs. block vs. expression
paulo-ferraz-oliveira Jun 16, 2025
67cfcc3
Fix it per rules
paulo-ferraz-oliveira Jun 16, 2025
98acb67
Make it slightly less confusing
paulo-ferraz-oliveira Jun 16, 2025
59d8511
Remove "taking it too far" in suggesting
paulo-ferraz-oliveira Jun 16, 2025
51f23e2
Propose something to replace throw/1
paulo-ferraz-oliveira Jun 16, 2025
b0d8678
Remove very-limited value -specs from tests + simplify it
paulo-ferraz-oliveira Jun 16, 2025
abe65ef
Be stricter with language
paulo-ferraz-oliveira Jun 16, 2025
37536b0
Expand elvis_result in preparation "for the future"
paulo-ferraz-oliveira Jun 16, 2025
9e0c685
Test the new elvis_result:new_item function
paulo-ferraz-oliveira Jun 16, 2025
1f34258
Implement expectations
paulo-ferraz-oliveira Jun 16, 2025
2720d30
Update god_module's description
paulo-ferraz-oliveira Jun 18, 2025
6989b9e
Update src/elvis_style.erl
paulo-ferraz-oliveira Jun 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ If you want to override the [pre-defined rules](#pre-defined-rules), for a given
to specify them in a `rules` key which is a list of items with the following structure
`{Module, Function, RuleConfig}`, or `{Module, Function}` - if the rule takes no configuration
values. You can also `disable` certain rules if you want to, by specifying them in the `rules` key
and passing `disable` as a third parameter.
and passing `disable` as a third argument.

#### Disabling Rules

Expand Down
4 changes: 2 additions & 2 deletions RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Most, if not all, of the rules will present (opinionated) documentation sections
- [No Operator With Same Values](doc_rules/elvis_style/no_operation_on_same_value.md)
- [No Redundant Blank Lines](doc_rules/elvis_text_style/no_redundant_blank_lines.md)
- [No `receive` Without Timeout](doc_rules/elvis_style/no_receive_without_timeout.md)
- [No Single-Clause Case Statements](doc_rules/elvis_style/no_single_clause_case.md)
- [No Single-Match Maybe Statements](doc_rules/elvis_style/no_single_match_maybe.md)
- [No Single-Clause Case Expressions](doc_rules/elvis_style/no_single_clause_case.md)
- [No Single-Match Maybe Blocks](doc_rules/elvis_style/no_single_match_maybe.md)
- [No Space After `#`](doc_rules/elvis_style/no_space_after_pound.md)
- [No Space](doc_rules/elvis_style/no_space.md)
- [No Spec With Records](doc_rules/elvis_style/no_spec_with_records.md)
Expand Down
10 changes: 5 additions & 5 deletions doc_rules/elvis_style/god_modules.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# God Modules ![](https://img.shields.io/badge/BEAM-yes-orange)

The number of functions in a module should be limited to a maximum threshold.
The number of exported functions in a module should be limited to a maximum threshold.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're actually only counting exported. Do we want an option for total (?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And actually that's not entirely true, since we're not looking at the actual exported functions, but the export nodes (if you're using compile(export_all) the count is not the same). Should we also account for this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(in 1/2 new issues, that is, not in this pull request)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well… Usage of export_all should raise a compiler warning if your project is properly set up. If not… you have larger problems than god_modules. I would not bother in adjusting the rule for that.

Regarding internal functions: When @marcelog came up with this rule, he was very intentional in only checking exported functions. The idea of the rule is not to prevent you from writing many functions in a module (quite the contrary, writing a module with many small functions instead of few large ones is preferrable), but to warn you if your module has too many responsibilities (i.e., it can be functionally split into multiple modules). I think the rule is fine as it is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, then we might need to adjust the documentation, just...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change pushed. (for clarity)


## Rationale

Limiting the number of functions in a module helps maintain clarity, modularity, and readability
in the codebase. A module with too many functions can become difficult to navigate, understand,
and maintain. By restricting the number of functions, you encourage smaller, more focused modules
that adhere to the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single-responsibility_principle)
Limiting the number of functions exported from a module helps maintain clarity, modularity, and
readability in the codebase. A module with too many exported functions can become difficult to
consume and understand, from an API perspective. By restricting the number of exported functions,
you encourage smaller, more focused modules that adhere to the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single-responsibility_principle)
(SRP). This also makes the code easier to test, debug, and extend.

## Options
Expand Down
2 changes: 1 addition & 1 deletion doc_rules/elvis_style/no_behavior_info.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# No Behavior Info ![](https://img.shields.io/badge/BEAM-yes-orange)

The use of `behaviour_info` (or `behavior_info`) attributes should be avoided; use `-callback`
annotations instead.
attributes instead.

## Avoid

Expand Down
4 changes: 2 additions & 2 deletions doc_rules/elvis_style/no_init_lists.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# No Init Lists [![](https://img.shields.io/badge/since-4.0.0-blue)](https://github.com/inaka/elvis_core/releases/tag/4.0.0) ![](https://img.shields.io/badge/BEAM-yes-orange)

Lists as the argument for the `init/1` callback should be avoided when implementing
`gen_*` behaviours; use a tuple, a map, or a record instead.
`gen_*` behaviors; use a tuple, a map, or a record instead.

## Avoid

Expand Down Expand Up @@ -35,7 +35,7 @@ init(#{ name := Name, timeout := Timeout }) ->

## Rationale

When implementing `gen_server`, `gen_statem`, or other `gen_*` behaviours, using a list as the
When implementing `gen_server`, `gen_statem`, or other `gen_*` behaviors, using a list as the
argument to the `init/1` callback is discouraged. Tuples, maps, and records provide clearer
structure, better pattern matching, and improved readability.
Lists are typically used for variable-length collections and may lead to ambiguous or less
Expand Down
2 changes: 1 addition & 1 deletion doc_rules/elvis_style/no_nested_hrls.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# No Types [![](https://img.shields.io/badge/since-4.1.0-blue)](https://github.com/inaka/elvis_core/releases/tag/4.1.0) ![](https://img.shields.io/badge/HRL--only-yes-magenta)
# No Nested Header Files [![](https://img.shields.io/badge/since-4.1.0-blue)](https://github.com/inaka/elvis_core/releases/tag/4.1.0) ![](https://img.shields.io/badge/HRL--only-yes-magenta)

`-include` and `-include_lib` attributes **in header files** should be avoided.

Expand Down
2 changes: 1 addition & 1 deletion doc_rules/elvis_style/no_receive_without_timeout.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ A `receive` block without a timeout will wait indefinitely if no matching messag
your timeout explicit you:

- avoid hanging processes.
- improve testability (deterministic behaviour under test failure conditions).
- improve testability (deterministic behavior under test failure conditions).
- ease debug and recovery.
- can implement retry and self-healing.
- potentially avoid denial-of-service scenarios where waits are exploited.
Expand Down
6 changes: 3 additions & 3 deletions doc_rules/elvis_style/no_single_clause_case.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# No Single-Clause Case Statements [![](https://img.shields.io/badge/since-3.0.0-blue)](https://github.com/inaka/elvis_core/releases/tag/3.0.0) ![](https://img.shields.io/badge/BEAM-yes-orange)
# No Single-Clause Case Expressions [![](https://img.shields.io/badge/since-3.0.0-blue)](https://github.com/inaka/elvis_core/releases/tag/3.0.0) ![](https://img.shields.io/badge/BEAM-yes-orange)

Single-clause case statements should be avoided.
Single-clause case expressions should be avoided.

## Avoid

Expand All @@ -22,7 +22,7 @@ do:something("else")
Using a `case` expression with only one clause is unnecessary and reduces code clarity. It adds
syntactic overhead without providing meaningful branching logic. In such cases, a let-style
assignment or direct pattern matching is typically more appropriate and idiomatic. Removing
single-clause case statements also improves readability and simplifies the control flow.
single-clause case expressions also improves readability and simplifies the control flow.

## Options

Expand Down
10 changes: 5 additions & 5 deletions doc_rules/elvis_style/no_single_match_maybe.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# No Single-Match Maybe Statements [![](https://img.shields.io/badge/since-4.1.0-blue)](https://github.com/inaka/elvis_core/releases/tag/4.1.0) ![](https://img.shields.io/badge/BEAM-yes-orange)
# No Single-Match Maybe Blocks [![](https://img.shields.io/badge/since-4.1.0-blue)](https://github.com/inaka/elvis_core/releases/tag/4.1.0) ![](https://img.shields.io/badge/BEAM-yes-orange)

Single-match maybe statements should be avoided.
Single-match `maybe` blocks should be avoided.

> [!NOTE]
> This rule only works under Erlang/OTP 27+.
Expand All @@ -19,7 +19,7 @@ end
{ok, A} = do:something()
```

Note that `maybe` statements with an `else` are perfectly acceptable, too:
Note that `maybe` blocks with an `else` are perfectly acceptable, too:

```erlang
maybe
Expand All @@ -31,10 +31,10 @@ end

## Rationale

Using a `maybe` expression with only one match is unnecessary and reduces code clarity. It adds
Using a `maybe` block with only one match is unnecessary and reduces code clarity. It adds
syntactic overhead without providing meaningful branching logic. In such cases, a let-style
assignment or direct pattern matching is typically more appropriate and idiomatic. Removing
single-match `maybe` statements also improves readability and simplifies the control flow.
single-match `maybe` block also improves readability and simplifies the control flow.

## Options

Expand Down
1 change: 0 additions & 1 deletion elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
{elvis_style, no_throw, disable},
{elvis_style, max_function_length, disable},
{elvis_style, max_function_clause_length, disable},
{elvis_style, no_macros, disable},
{elvis_style, max_module_length, #{ignore => [elvis_style]}},
{elvis_style, no_common_caveats_call, #{
ignore => [
Expand Down
6 changes: 2 additions & 4 deletions src/elvis_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
-type config() :: map().
-type configs() :: [config()].

-define(DEFAULT_FILTER, "*.erl").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -176,7 +174,7 @@ filter(Config) when is_list(Config) ->
filter(#{filter := Filter}) ->
Filter;
filter(#{}) ->
?DEFAULT_FILTER.
"*.erl".

-spec files(RuleGroup :: configs() | config()) -> [elvis_file:file()].
files(RuleGroup) when is_list(RuleGroup) ->
Expand Down Expand Up @@ -225,7 +223,7 @@ resolve_files(RuleGroup, Files) ->
warn,
"Searching for files in ~p, for ruleset ~p, "
"with filter ~p, yielded none. "
"Update your configuration.",
"Update your configuration",
[Dirs, RuleSet, Filter]
),
ok = elvis_result:print_results([Error]);
Expand Down
2 changes: 1 addition & 1 deletion src/elvis_core.erl
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ apply_rule({Module, Function, ConfigArgs}, {Result, Config, File}) ->
end
catch
_:Reason:Stacktrace ->
Msg = "'~p' while applying rule '~p': ~p.",
Msg = "'~p' while applying rule '~p': ~p",
elvis_result:new(error, Msg, [Reason, Function, Stacktrace])
end,
{[RuleResult | Result], Config, File}.
Expand Down
19 changes: 14 additions & 5 deletions src/elvis_gitignore.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

-export([required_patterns/3, forbidden_patterns/3, default/1]).

-define(REQUIRED_PATTERN, "Your .gitignore file should contain pattern '~s'.").
-define(FORBIDDEN_PATTERN, "Your .gitignore file should not contain pattern '~s'.").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Default values
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -83,9 +80,21 @@ check_patterns_in_lines(Lines, [Pattern | Rest], Results0, Mode) ->
true ->
Results0;
false when Mode =:= required ->
[elvis_result:new(item, ?REQUIRED_PATTERN, [Pattern]) | Results0];
[
elvis_result:new_item(
"Your .gitignore file should contain pattern '~s'",
[Pattern]
)
| Results0
];
false when Mode =:= forbidden ->
[elvis_result:new(item, ?FORBIDDEN_PATTERN, [Pattern]) | Results0]
[
elvis_result:new_item(
"Your .gitignore file should not contain pattern '~s'",
[Pattern]
)
| Results0
]
end,
check_patterns_in_lines(Lines, Rest, Results, Mode).

Expand Down
87 changes: 48 additions & 39 deletions src/elvis_project.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,6 @@

-export_type([protocol_for_deps_config/0]).

-define(DEP_BRANCH,
"Dependency '~s' uses a branch. "
"Please change this to a tag or specific commit."
).
-define(DEP_NO_GIT,
"Dependency '~s' is not using appropriate protocol, "
"please change this to something like '~s'"
).
-define(OLD_CONFIG_FORMAT,
"The current Elvis configuration file has an outdated format. "
"Please check Elvis's GitHub repository to find out what the "
"new format is."
).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Default values
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -49,11 +35,32 @@ protocol_for_deps(_Config, Target, RuleConfig) ->
Deps = get_deps(Target),
NoHexDeps = lists:filter(fun(Dep) -> not is_hex_dep(Dep) end, Deps),
BadDeps = lists:filter(fun(Dep) -> is_not_git_dep(Dep, Regex) end, NoHexDeps),
lists:flatmap(
fun(Line) -> dep_to_result(Line, ?DEP_NO_GIT, {IgnoreDeps, Regex}) end,
lists:filtermap(
fun(Line) ->
AppName = appname_from_line(Line),

case lists:member(AppName, IgnoreDeps) of
true ->
false;
false ->
{true,
elvis_result:new_item(
"Dependency '~s' is not using appropriate protocol; prefer "
"respecting regular expression '~s'",
[AppName, Regex]
)}
end
end,
BadDeps
).

appname_from_line({AppName, _}) ->
AppName;
appname_from_line({AppName, _, _GitInfo}) ->
AppName;
appname_from_line({AppName, _Vsn, _GitInfo, _Opts}) ->
AppName.

-spec no_branch_deps(
elvis_config:config(),
elvis_file:file(),
Expand All @@ -64,7 +71,24 @@ no_branch_deps(_Config, Target, RuleConfig) ->
IgnoreDeps = option(ignore, RuleConfig, no_branch_deps),
Deps = get_deps(Target),
BadDeps = lists:filter(fun is_branch_dep/1, Deps),
lists:flatmap(fun(Line) -> dep_to_result(Line, ?DEP_BRANCH, IgnoreDeps) end, BadDeps).
lists:filtermap(
fun(Line) ->
AppName = appname_from_line(Line),

case lists:member(AppName, IgnoreDeps) of
true ->
false;
false ->
{true, [
elvis_result:new_item(
"Dependency '~s' uses a branch; prefer a tag or a specific commit",
[AppName]
)
]}
end
end,
BadDeps
).

-spec old_configuration_format(
elvis_config:config(),
Expand All @@ -83,7 +107,13 @@ old_configuration_format(_Config, Target, _RuleConfig) ->
false ->
[];
true ->
[elvis_result:new(item, ?OLD_CONFIG_FORMAT, [])]
[
elvis_result:new_item(
"The current Elvis configuration file has an outdated format. "
"Please check Elvis's GitHub repository to find out what the "
"new format is"
)
]
end
end.

Expand Down Expand Up @@ -165,27 +195,6 @@ is_not_git_dep({_AppName, _Vsn, {_SCM, Url, {BranchTagOrRefType, _Branch}}, _Opt
->
nomatch == re:run(Url, Regex, []).

dep_to_result({AppName, _}, Message, {IgnoreDeps, Regex}) ->
case lists:member(AppName, IgnoreDeps) of
true ->
[];
false ->
[elvis_result:new(item, Message, [AppName, Regex])]
end;
dep_to_result({AppName, _}, Message, IgnoreDeps) ->
case lists:member(AppName, IgnoreDeps) of
true ->
[];
false ->
[elvis_result:new(item, Message, [AppName])]
end;
dep_to_result({AppName, _, GitInfo}, Message, {IgnoreDeps, Regex}) ->
dep_to_result({AppName, GitInfo}, Message, {IgnoreDeps, Regex});
dep_to_result({AppName, _, GitInfo}, Message, IgnoreDeps) ->
dep_to_result({AppName, GitInfo}, Message, IgnoreDeps);
dep_to_result({AppName, _Vsn, GitInfo, _Opts}, Message, IgnoreDeps) ->
dep_to_result({AppName, GitInfo}, Message, IgnoreDeps).

%% Old config

is_old_config(ElvisConfig) ->
Expand Down
Loading
Loading