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
88 commits
Select commit Hold shift + click to select a range
b2cd09f
Simplify it for default values
paulo-ferraz-oliveira Jun 18, 2025
323e776
Allow for new find_by_types/4 (with filter)
paulo-ferraz-oliveira Jun 19, 2025
2b3cdc0
Simplify further patterns
paulo-ferraz-oliveira Jun 19, 2025
9ed7fe7
Continue the pursuit for patterns
paulo-ferraz-oliveira Jun 19, 2025
73d4ece
Fix doc where lacking
paulo-ferraz-oliveira Jun 19, 2025
bfbd330
Update/remove spec/type where unwarranted
paulo-ferraz-oliveira Jun 19, 2025
eb39d07
Stay the course of simplification
paulo-ferraz-oliveira Jun 19, 2025
84d00c6
Propose a way to simplify writing rules: first pass
paulo-ferraz-oliveira Jun 19, 2025
53890f1
Find further defaults and common patterns we can tweak
paulo-ferraz-oliveira Jun 19, 2025
b52f3f1
Propose a way to simplify writing rules: second pass
paulo-ferraz-oliveira Jun 19, 2025
17a7f5b
New format: no_nested_hrls
paulo-ferraz-oliveira Jun 20, 2025
eb8dd74
New format: no_specs
paulo-ferraz-oliveira Jun 20, 2025
bc1fb44
New format: no_block_expressions (introduces tokens_as_content/1)
paulo-ferraz-oliveira Jun 20, 2025
2b67773
New format: no_macros
paulo-ferraz-oliveira Jun 20, 2025
b15b8bf
New format: macro_names
paulo-ferraz-oliveira Jun 20, 2025
f1ae720
New format: no_types
paulo-ferraz-oliveira Jun 20, 2025
7bd7bb1
New format: no_if_expressions
paulo-ferraz-oliveira Jun 20, 2025
979bd1e
Fix elvis_code:find/1 (typespec) and stop reversing list
paulo-ferraz-oliveira Jun 20, 2025
4b72014
Tweak specs a bit (add more zipper support)
paulo-ferraz-oliveira Jun 21, 2025
f5dad4e
New format: used_ignored_vars
paulo-ferraz-oliveira Jun 21, 2025
a0ab883
New format: no_behavior_info
paulo-ferraz-oliveira Jun 21, 2025
59485e6
New format: invalid_dynamic_call
paulo-ferraz-oliveira Jun 21, 2025
ef8e22f
New format: no_spec_with_records
paulo-ferraz-oliveira Jun 21, 2025
3c9c266
Minor format changes
paulo-ferraz-oliveira Jun 21, 2025
7693cfb
New format: max_function_clause_length
paulo-ferraz-oliveira Jun 21, 2025
253807b
Fix reading options for max_function_clause_length
paulo-ferraz-oliveira Jun 21, 2025
2a15cfa
New format: max_function_length
paulo-ferraz-oliveira Jun 21, 2025
f1aeafc
Tweak a few messages (make limits more explicit)
paulo-ferraz-oliveira Jun 21, 2025
08f820c
New format: no_nested_try_catch
paulo-ferraz-oliveira Jun 21, 2025
5f4aadb
New format: no_successive_maps
paulo-ferraz-oliveira Jun 21, 2025
4900424
Simplify for already-tested condition
paulo-ferraz-oliveira Jun 21, 2025
5c2cb6b
New format: no_init_lists
paulo-ferraz-oliveira Jun 21, 2025
8720c70
New format: ms_transform_included
paulo-ferraz-oliveira Jun 21, 2025
bc21127
New format: no_boolean_in_comparison
paulo-ferraz-oliveira Jun 21, 2025
baf7717
New format: no_receive_without_timeout
paulo-ferraz-oliveira Jun 21, 2025
4829a1f
New format: no_operation_on_same_value
paulo-ferraz-oliveira Jun 21, 2025
2b72470
New format: no_throw
paulo-ferraz-oliveira Jun 21, 2025
10362e0
New format: no_dollar_space
paulo-ferraz-oliveira Jun 21, 2025
44af5d8
New format: no_author
paulo-ferraz-oliveira Jun 21, 2025
cc91beb
New format: no_import
paulo-ferraz-oliveira Jun 21, 2025
88d2ac5
New format: no_catch_expressions
paulo-ferraz-oliveira Jun 21, 2025
ee573b2
New format: no_single_clause_case
paulo-ferraz-oliveira Jun 21, 2025
47c25e2
New format: behaviour_spelling
paulo-ferraz-oliveira Jun 21, 2025
bf8f081
New format: param_pattern_matching
paulo-ferraz-oliveira Jun 21, 2025
6bfc95e
New format: consistent_generic_type
paulo-ferraz-oliveira Jun 21, 2025
e1eee89
New format: always_shortcircuit
paulo-ferraz-oliveira Jun 22, 2025
9b4d88b
Remove unnecessary reference to generic doc.
paulo-ferraz-oliveira Jun 22, 2025
d62e21f
New format: module_naming_convention
paulo-ferraz-oliveira Jun 22, 2025
a55d4ec
New format: max_module_length
paulo-ferraz-oliveira Jun 22, 2025
419b0a7
New format: max_anonymous_function_arity
paulo-ferraz-oliveira Jun 22, 2025
8af0e5a
New format: max_function_arity
paulo-ferraz-oliveira Jun 22, 2025
c1e4ab8
New format: no_single_match_maybe
paulo-ferraz-oliveira Jun 22, 2025
a074a80
New format: no_match_in_condition
paulo-ferraz-oliveira Jun 22, 2025
af37155
New format: export_used_types
paulo-ferraz-oliveira Jun 22, 2025
f9fde79
New format: private_data_types
paulo-ferraz-oliveira Jun 22, 2025
5f95ee6
Prevent copy-paste errors (and also tightly couple rule and rule name)
paulo-ferraz-oliveira Jun 22, 2025
8d62276
Vastly simplify the :find API
paulo-ferraz-oliveira Jun 22, 2025
95affe2
Review code that was left for review later
paulo-ferraz-oliveira Jun 22, 2025
9833039
New format: function_naming_convention
paulo-ferraz-oliveira Jun 22, 2025
e54ccb2
New function: variable_naming_convention
paulo-ferraz-oliveira Jun 22, 2025
64d9767
New format: atom_naming_convention
paulo-ferraz-oliveira Jun 22, 2025
0e03e00
Minor tweaks to comments and docs
paulo-ferraz-oliveira Jun 22, 2025
12794d3
Improve in preparation for further tweak operations
paulo-ferraz-oliveira Jun 22, 2025
cd61242
Let rebar3 control the output of compilation errors
paulo-ferraz-oliveira Jun 22, 2025
054c632
New format: no_space_after_pound, no_space, operator_spaces
paulo-ferraz-oliveira Jun 22, 2025
faa6e2a
New format: nesting_level
paulo-ferraz-oliveira Jun 22, 2025
674642b
Handle regex in similar manner across the board
paulo-ferraz-oliveira Jun 23, 2025
cfe3b12
Allow Dialyzer to know when we're breaking opacity or not
paulo-ferraz-oliveira Jun 23, 2025
9d24967
New format: god_modules
paulo-ferraz-oliveira Jun 23, 2025
cf96668
New format: state_record_and_type
paulo-ferraz-oliveira Jun 23, 2025
f6d1345
New format: no_call, no_debug_call, no_common_caveats_call
paulo-ferraz-oliveira Jun 23, 2025
77e4cad
New format: numeric_format
paulo-ferraz-oliveira Jun 23, 2025
d9f46bd
New format: consistent_variable_casing
paulo-ferraz-oliveira Jun 23, 2025
c0dc585
Put it back again: concentrate on important stuff
paulo-ferraz-oliveira Jun 23, 2025
1f5178f
New format: dont_repeat_yourself (and drop some specs)
paulo-ferraz-oliveira Jun 23, 2025
6b24f9f
Fix printing dont_repeat_yourself output
paulo-ferraz-oliveira Jun 23, 2025
4621a46
Act on self-review
paulo-ferraz-oliveira Jun 23, 2025
0c9289e
Finalise it for review
paulo-ferraz-oliveira Jun 23, 2025
c4deb1b
Fix it for OTP 26
paulo-ferraz-oliveira Jun 23, 2025
803ae66
Act on self-review
paulo-ferraz-oliveira Jun 23, 2025
0945d30
Make it more explicit where we find compulsory-ness
paulo-ferraz-oliveira Jun 23, 2025
c9da325
code_zipper > zipper, as per review comment
paulo-ferraz-oliveira Jun 25, 2025
9a86a55
Improve (internal) API consistency, as per review comment
paulo-ferraz-oliveira Jun 25, 2025
1d0af89
Make it more generic, as per review comment
paulo-ferraz-oliveira Jun 25, 2025
82f06a1
Trim it, as per review comment
paulo-ferraz-oliveira Jun 26, 2025
aef5384
Simplify it, as per review comment
paulo-ferraz-oliveira Jun 26, 2025
685f933
Remove dead code, as per review comment
paulo-ferraz-oliveira Jun 26, 2025
96c4cb5
Remove function, as per review comment
paulo-ferraz-oliveira Jun 26, 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
5 changes: 3 additions & 2 deletions doc_rules/elvis_project/no_branch_deps.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ branch heads.

## Options

- None.
- `ignore :: [atom()]`
- default: `[]`

## Example configuration

```erlang
{elvis_project, no_branch_deps, #{}}
{elvis_project, no_branch_deps, #{ ignore => [] }}
```
5 changes: 4 additions & 1 deletion doc_rules/elvis_project/protocol_for_deps.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ obscure errors during fetching or compilation. It also increases consistency acr

- `regex :: string()`
- default: `"^(https://|git://|\\d+(\\.\\d+)*)"`
- `ignore :: [atom()]`
- default: `[]`

`regex` was
`"https://.*|[0-9]+([.][0-9]+)*)"` until [4.0.0](https://github.com/inaka/elvis_core/releases/tag/4.0.0).

## Example configuration

```erlang
{elvis_project, protocol_for_deps, #{ regex => "^(https://|git://|\\d+(\\.\\d+)*)" }}
{elvis_project, protocol_for_deps, #{ regex => "^(https://|git://|\\d+(\\.\\d+)*)"
, ignore => [] }}
```
3 changes: 2 additions & 1 deletion doc_rules/elvis_style/module_naming_convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ By defining a regular expression for naming modules you increase consistency acr

```erlang
{elvis_style, module_naming_convention, #{ regex => "^[a-z](_?[a-z0-9]+)*(_SUITE)?$"
, forbidden_regex => undefined }}
, forbidden_regex => undefined
}}
```
4 changes: 2 additions & 2 deletions elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
ignore => [
{elvis_file, module, 1},
{elvis_style, is_call, 2},
{elvis_style, no_macros, 3},
{elvis_text_style, needs_quoting, 1}
{elvis_style, is_allowed_macro, 2},
{elvis_text_style, doesnt_need_quotes, 1}
]
}}
],
Expand Down
1 change: 0 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
warn_export_vars,
warnings_as_errors,
verbose,
report,
debug_info
]}.

Expand Down
188 changes: 94 additions & 94 deletions src/elvis_code.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,84 @@

%% General
-export([
find/2,
find/3,
find_by_location/2,
find_by_types/2,
find_by_types/3,
find_by_types_in_tokens/2,
find_token/2,
code_zipper/1,
code_zipper/2
find/1,
zipper/1,
root/1
]).
%% Specific
-export([
print_node/1,
print_node/2
]).

-export_type([find_options/0]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-type find_options() :: #{mode => node | zipper, traverse => content | all}.
-type find_options() :: #{filtered_from => node | zipper, traverse => content | all}.

-type tree_node() :: ktn_code:tree_node().
-type tree_node_type() :: ktn_code:tree_node_type().
-type tree_node_zipper() :: zipper:zipper(tree_node()).

-export_type([find_options/0, tree_node/0, tree_node_type/0, tree_node_zipper/0]).

-spec find(Options) -> {nodes, [Node]} | {zippers, [Zipper]} when
Options :: #{
% undefined means "all types"
of_types := [tree_node_type()] | undefined,
inside := Node,
% undefined means "don't filter"
filtered_by => fun((Node | Zipper) -> boolean()),
filtered_from => node | zipper,
traverse => content | all
},
Node :: tree_node(),
Zipper :: tree_node_zipper().
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),

NonFilteredResults = find(
fun(NodeOrZipper) ->
Node =
case FilteredFrom of
_ when OfTypes =:= undefined ->
undefined;
node ->
NodeOrZipper;
zipper ->
Zipper = NodeOrZipper,
zipper:node(Zipper)
end,
OfTypes =:= undefined orelse lists:member(ktn_code:type(Node), OfTypes)
end,
Inside,
FilteredFrom,
Traverse
),

Results =
case FilteredBy of
undefined ->
NonFilteredResults;
_ ->
[Result || Result <- NonFilteredResults, FilteredBy(Result)]
end,

%% @doc Same as calling find/3 with `#{mode => node, traverse => content}' as
%% the options map.
%% @end
-spec find(fun((zipper:zipper(_)) -> boolean()), ktn_code:tree_node()) ->
[ktn_code:tree_node()].
find(Pred, Root) ->
find(Pred, Root, #{}).
case FilteredFrom of
node ->
{nodes, Results};
zipper ->
{zippers, Results}
end.

%% @doc Find all nodes in the tree for which the predicate function returns
%% `true'. The options map has two keys:
%% <ul>
%% <li>
%% - `mode': when the value `node' is specified the predicate function
%% - `filtered_from': when the value `node' is specified the predicate function
%% receives a tree_node() as its argument. When `zipper' is specified
%% the argument is the zipper location for the current node.
%% </li>
Expand All @@ -50,29 +91,24 @@ find(Pred, Root) ->
%% </li>
%% </ul>
%% @end
-spec find(fun((zipper:zipper(_)) -> boolean()), ktn_code:tree_node(), find_options()) ->
[ktn_code:tree_node()].
find(Pred, Root, Opts) ->
Mode = maps:get(mode, Opts, node),
ZipperMode = maps:get(traverse, Opts, content),
Zipper = code_zipper(Root, ZipperMode),
Results = find(Pred, Zipper, [], Mode),
-spec find(fun((tree_node_zipper()) -> boolean()), tree_node(), node | zipper, content | all) ->
[tree_node() | tree_node_zipper()].
find(Pred, Root, FilteredFrom, Traverse) ->
Zipper = zipper(Root, Traverse),
Results = find_with_zipper(Pred, Zipper, [], FilteredFrom),
lists:reverse(Results).

-spec code_zipper(ktn_code:tree_node()) -> zipper:zipper(_).
code_zipper(Root) ->
code_zipper(Root, content).

-spec code_zipper(ktn_code:tree_node(), content | all) -> zipper:zipper(_).
code_zipper(Root, Mode) ->
case Mode of
content ->
content_zipper(Root);
all ->
all_zipper(Root)
end.
-spec zipper(tree_node()) -> tree_node_zipper().
zipper(Root) ->
zipper(Root, content).

-spec content_zipper(ktn_code:tree_node()) -> zipper:zipper(_).
-spec zipper(tree_node(), content | all) -> tree_node_zipper().
zipper(Root, content) ->
content_zipper(Root);
zipper(Root, all) ->
all_zipper(Root).

-spec content_zipper(tree_node()) -> tree_node_zipper().
content_zipper(Root) ->
IsBranch =
fun
Expand All @@ -85,7 +121,7 @@ content_zipper(Root) ->
MakeNode = fun(Node, Content) -> Node#{content => Content} end,
zipper:new(IsBranch, Children, MakeNode, Root).

-spec all_zipper(ktn_code:tree_node()) -> zipper:zipper(_).
-spec all_zipper(tree_node()) -> tree_node_zipper().
all_zipper(Root) ->
IsBranch =
fun(#{} = Node) -> ktn_code:content(Node) =/= [] orelse maps:is_key(node_attrs, Node) end,
Expand All @@ -106,7 +142,7 @@ all_zipper(Root) ->
MakeNode = fun(Node, _) -> Node end,
zipper:new(IsBranch, Children, MakeNode, Root).

find(Pred, Zipper, Results, Mode) ->
find_with_zipper(Pred, Zipper, Results, Mode) ->
case zipper:is_end(Zipper) of
true ->
Results;
Expand All @@ -121,70 +157,34 @@ find(Pred, Zipper, Results, Mode) ->
NewResults =
case Pred(Value) of
true ->
[zipper:node(Zipper) | Results];
[Value | Results];
false ->
Results
end,
find(Pred, zipper:next(Zipper), NewResults, Mode)
end.

-spec find_by_location(ktn_code:tree_node(), {integer(), integer()}) ->
not_found | {ok, ktn_code:tree_node()}.
find_by_location(Root, Location) ->
Fun = fun(Node) -> is_at_location(Node, Location) end,
case find(Fun, Root, #{traverse => all}) of
[] ->
not_found;
[Node | _] ->
{ok, Node}
find_with_zipper(Pred, zipper:next(Zipper), NewResults, Mode)
end.

find_by_types(Types, Root) ->
find_by_types(Types, Root, #{}).

find_by_types(Types, Root, Opts) ->
find(
fun(Node) ->
lists:member(ktn_code:type(Node), Types)
end,
Root,
Opts
).

find_by_types_in_tokens(Types, Root) ->
Tokens = ktn_code:attr(tokens, Root),
lists:filter(
fun(Node) ->
lists:member(ktn_code:type(Node), Types)
end,
Tokens
).

is_at_location(#{attrs := #{location := {Line, NodeCol}}} = Node, {Line, Column}) ->
Text = ktn_code:attr(text, Node),
Length = length(Text),
NodeCol =< Column andalso Column < NodeCol + Length;
is_at_location(_, _) ->
false.

-spec find_token(ktn_code:tree_node(), {integer(), integer()}) -> not_found | {ok, map()}.
find_token(Root, Location) ->
Fun = fun(Token) -> is_at_location(Token, Location) end,
Tokens = ktn_code:attr(tokens, Root),
case lists:filter(Fun, Tokens) of
[] ->
not_found;
[Token | _] ->
{ok, Token}
-spec root({Config, Target, RuleConfig}) -> Res when
Config :: elvis_config:config(),
Target :: elvis_file:file(),
RuleConfig :: (Options :: #{atom() => term()}),
Res :: ktn_code:tree_node().
root({Config, Target, RuleConfig}) ->
{Root0, File0} = elvis_file:parse_tree(Config, Target, RuleConfig),
case maps:get(ruleset, Config, undefined) of
Ruleset when Ruleset =:= beam_files; Ruleset =:= beam_files_strict ->
maps:get(abstract_parse_tree, File0);
_ ->
Root0
end.

%% @doc Debugging utility function.
-spec print_node(ktn_code:tree_node()) -> ok.
-spec print_node(tree_node()) -> ok.
print_node(Node) ->
print_node(Node, 0).

%% @doc Debugging utility function.
-spec print_node(ktn_code:tree_node(), integer()) -> ok.
-spec print_node(tree_node(), integer()) -> ok.
print_node(#{type := Type} = Node, CurrentLevel) ->
Type = ktn_code:type(Node),
Indentation = lists:duplicate(CurrentLevel * 4, $\s),
Expand Down
12 changes: 6 additions & 6 deletions src/elvis_core.erl
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ apply_rules(Config, File) ->
elvis_result:new(file, File, RulesResults).

merge_rules({file, ParseTree}, ElvisConfigRules) ->
ElvisAttrs =
elvis_code:find(fun is_elvis_attr/1, ParseTree, #{traverse => content, mode => node}),
{nodes, ElvisAttrs} =
elvis_code:find(#{
of_types => [elvis],
inside => ParseTree
}),
ElvisAttrRules = elvis_attr_rules(ElvisAttrs),
elvis_config:merge_rules(ElvisAttrRules, ElvisConfigRules).

is_elvis_attr(Node) ->
ktn_code:type(Node) =:= elvis.

elvis_attr_rules([] = _ElvisAttrs) ->
[];
elvis_attr_rules(ElvisAttrs) ->
Expand Down Expand Up @@ -216,7 +216,7 @@ apply_rule({Module, Function, ConfigArgs}, {Result, Config, File}) ->
ConfigMap#{ignore => lists:delete(AnalyzedModule, Ignores)},
ConfigArgs
),
Results = Module:Function(Config, File, FilteredConfigMap),
Results = Module:Function({Config, File, FilteredConfigMap}),
SortFun = fun(#{line_num := L1}, #{line_num := L2}) -> L1 =< L2 end,
SortResults = lists:sort(SortFun, Results),
elvis_result:new(rule, {Module, Function}, SortResults);
Expand Down
Loading
Loading