Skip to content

Commit

Permalink
Merge branch 'main' into atom_naming_convention-only-atoms
Browse files Browse the repository at this point in the history
  • Loading branch information
elbrujohalcon authored Dec 16, 2024
2 parents b3b756e + ecc035a commit 2feea37
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 36 deletions.
15 changes: 11 additions & 4 deletions doc_rules/elvis_style/atom_naming_convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@

(since [1.0.0](https://github.com/inaka/elvis_core/releases/tag/1.0.0))

All atoms should be named according to the regular expression provided. Atoms enclosed in
apostrophes have special meaning and are thus handled by a different configuration option (use
`same` if you want the same value as `regex`).
All atoms should be named according to the provided regular expression,
except if they match with a defined `forbidden_regex`.
Atoms enclosed in apostrophes have special meaning and are thus handled
by a different configuration option (use `same` if you want the same value as `regex`).
To define forbidden enclosed atoms (like the ones in `forbidden_regex` apply for `regex`),
use `forbidden_enclosed_regex`(use `same` if you want the same value as `forbidden_regex`).

> Works on `.beam` file? Yes!
## Options

- `regex :: string()`.
- default: `"^([a-z][a-z0-9]*_?)*(_SUITE)?$"`.
- `enclosed_atoms :: string()`.
- `enclosed_atoms :: string() | same`.
- default: `".*"`.
- `forbidden_regex :: string() | undefined`.
- default: `undefined`.
- `forbidden_enclosed_regex :: string() | undefined | same`.
- default: `undefined`.

## Example

Expand Down
5 changes: 4 additions & 1 deletion doc_rules/elvis_style/function_naming_convention.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Function Naming Convention

All functions should be named according to the regular expression provided.
All functions should be named according to the provided regular expression,
except if they match with a defined `forbidden_regex`.

> Works on `.beam` file? Yes!
## Options

- `regex :: string()`.
- default: `"^[a-z](_?[a-z0-9]+)*$"`.
- `forbidden_regex :: string() | undefined`.
- default: `undefined`.

## Example

Expand Down
5 changes: 4 additions & 1 deletion doc_rules/elvis_style/module_naming_convention.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Module Naming Convention

All modules should be named according to the regular expression provided.
All modules should be named according to the provided regular expression,
except if they match with a defined `forbidden_regex`.

> Works on `.beam` file? Yes!
## Options

- `regex :: string()`.
- default: `"^[a-z](_?[a-z0-9]+)*(_SUITE)?$"`.
- `forbidden_regex :: string() | undefined`.
- default: `undefined`.

## Example

Expand Down
5 changes: 4 additions & 1 deletion doc_rules/elvis_style/variable_naming_convention.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Variable Naming Convention

All variables should be named according to the regular expression provided.
All variables should be named according to the provided regular expression,
except if they match with a defined `forbidden_regex`.

> Works on `.beam` file? Yes!
## Options

- `regex :: string()`.
- default: `"^_?([A-Z][0-9a-zA-Z]*)$"`.
- `forbidden_regex :: string() | undefined`.
- default: `undefined`.

## Example

Expand Down
153 changes: 128 additions & 25 deletions src/elvis_style.erl
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,24 @@
"Use the '-callback' attribute instead of 'behavior_info/1' "
"on line ~p.").
-define(FUNCTION_NAMING_CONVENTION_MSG,
"The function ~p does not respect the format defined by the "
"The function ~p's name does not respect the format defined by the "
"regular expression '~p'.").
-define(FORBIDDEN_FUNCTION_NAMING_CONVENTION_MSG,
"The function ~p's name is written in a forbidden format"
"defined by the regular expression '~p'.").
-define(VARIABLE_NAMING_CONVENTION_MSG,
"The variable ~p on line ~p does not respect the format "
"The variable ~p's name, on line ~p does not respect the format "
"defined by the regular expression '~p'.").
-define(FORBIDDEN_VARIABLE_NAMING_CONVENTION_MSG,
"The variable ~p's name on line ~p is written in a forbidden the format "
"defined by the regular expression '~p'.").
-define(CONSISTENT_VARIABLE_CASING_MSG,
"Variable ~ts (first used in line ~p) is written in different ways within the module: ~p.").
-define(MODULE_NAMING_CONVENTION_MSG,
"The module ~p does not respect the format defined by the "
"The module ~p's name does not respect the format defined by the "
"regular expression '~p'.").
-define(FORBIDDEN_MODULE_NAMING_CONVENTION_MSG,
"The module ~p's name is written in a forbidden format defined by the "
"regular expression '~p'.").
-define(STATE_RECORD_MISSING_MSG,
"This module implements an OTP behavior but is missing "
Expand Down Expand Up @@ -114,7 +123,10 @@
-define(NO_SUCCESSIVE_MAPS_MSG,
"Found map update after map construction/update at line ~p.").
-define(ATOM_NAMING_CONVENTION_MSG,
"Atom ~p on line ~p does not respect the format "
"Atom ~p's name, on line ~p does not respect the format "
"defined by the regular expression '~p'.").
-define(FORBIDDEN_ATOM_NAMING_CONVENTION_MSG,
"Atom ~p on line ~p's name is written in a forbidden format "
"defined by the regular expression '~p'.").
-define(NO_THROW_MSG, "Usage of throw/1 on line ~p is not recommended").
-define(NO_DOLLAR_SPACE_MSG,
Expand Down Expand Up @@ -183,11 +195,11 @@ default(nesting_level) ->
default(god_modules) ->
#{limit => 25};
default(function_naming_convention) ->
#{regex => "^[a-z](_?[a-z0-9]+)*$"};
#{regex => "^[a-z](_?[a-z0-9]+)*$", forbidden_regex => undefined};
default(variable_naming_convention) ->
#{regex => "^_?([A-Z][0-9a-zA-Z]*)$"};
#{regex => "^_?([A-Z][0-9a-zA-Z]*)$", forbidden_regex => undefined};
default(module_naming_convention) ->
#{regex => "^[a-z](_?[a-z0-9]+)*(_SUITE)?$"};
#{regex => "^[a-z](_?[a-z0-9]+)*(_SUITE)?$", forbidden_regex => undefined};
default(dont_repeat_yourself) ->
#{min_complexity => 10};
default(max_module_length) ->
Expand Down Expand Up @@ -225,7 +237,10 @@ default(no_common_caveats_call) ->
{timer, send_interval, 3},
{erlang, size, 1}]};
default(atom_naming_convention) ->
#{regex => "^[a-z](_?[a-z0-9]+)*(_SUITE)?$", enclosed_atoms => ".*"};
#{regex => "^[a-z](_?[a-z0-9]+)*(_SUITE)?$",
enclosed_atoms => ".*",
forbidden_regex => undefined,
forbidden_enclosed_regex => undefined};
%% Not restrictive. Those who want more restrictions can set it like "^[^_]*$"
default(numeric_format) ->
#{regex => ".*",
Expand Down Expand Up @@ -292,22 +307,38 @@ default(RuleWithEmptyDefault)
[elvis_result:item()].
function_naming_convention(Config, Target, RuleConfig) ->
Regex = option(regex, RuleConfig, function_naming_convention),
ForbiddenRegex = option(forbidden_regex, RuleConfig, function_naming_convention),
Root = get_root(Config, Target, RuleConfig),
FunctionNames0 = elvis_code:function_names(Root),
errors_for_function_names(Regex, FunctionNames0).
errors_for_function_names(Regex, ForbiddenRegex, FunctionNames0).

errors_for_function_names(_Regex, []) ->
errors_for_function_names(_Regex, _ForbiddenRegex, []) ->
[];
errors_for_function_names(Regex, [FunctionName | RemainingFuncNames]) ->
errors_for_function_names(Regex, ForbiddenRegex, [FunctionName | RemainingFuncNames]) ->
FunctionNameStr = unicode:characters_to_list(atom_to_list(FunctionName), unicode),
case re:run(FunctionNameStr, Regex, [unicode]) of
nomatch ->
Msg = ?FUNCTION_NAMING_CONVENTION_MSG,
Info = [FunctionNameStr, Regex],
Result = elvis_result:new(item, Msg, Info, 1),
[Result | errors_for_function_names(Regex, RemainingFuncNames)];
[Result | errors_for_function_names(Regex, ForbiddenRegex, RemainingFuncNames)];
{match, _} ->
errors_for_function_names(Regex, RemainingFuncNames)
case ForbiddenRegex of
undefined ->
errors_for_function_names(Regex, ForbiddenRegex, RemainingFuncNames);
ForbiddenRegex ->
case re:run(FunctionNameStr, ForbiddenRegex, [unicode]) of
{match, _} ->
Msg = ?FORBIDDEN_FUNCTION_NAMING_CONVENTION_MSG,
Info = [FunctionNameStr, Regex],
Result = elvis_result:new(item, Msg, Info, 1),
[Result | errors_for_function_names(Regex,
ForbiddenRegex,
RemainingFuncNames)];
nomatch ->
errors_for_function_names(Regex, ForbiddenRegex, RemainingFuncNames)
end
end
end.

-type consistent_variable_casing_config() :: #{ignore => [ignorable()]}.
Expand Down Expand Up @@ -362,9 +393,10 @@ check_variable_casing_consistency({_,
[elvis_result:item()].
variable_naming_convention(Config, Target, RuleConfig) ->
Regex = option(regex, RuleConfig, variable_naming_convention),
ForbiddenRegex = option(forbidden_regex, RuleConfig, variable_naming_convention),
Root = get_root(Config, Target, RuleConfig),
Vars = elvis_code:find(fun is_var/1, Root, #{traverse => all, mode => zipper}),
check_variables_name(Regex, Vars).
check_variables_name(Regex, ForbiddenRegex, Vars).

-type macro_names_config() :: #{ignore => [ignorable()], regex => string()}.

Expand Down Expand Up @@ -724,6 +756,7 @@ no_behavior_info(Config, Target, RuleConfig) ->
[elvis_result:item()].
module_naming_convention(Config, Target, RuleConfig) ->
Regex = option(regex, RuleConfig, module_naming_convention),
ForbiddenRegex = option(forbidden_regex, RuleConfig, module_naming_convention),
IgnoreModules = option(ignore, RuleConfig, module_naming_convention),

Root = get_root(Config, Target, RuleConfig),
Expand All @@ -739,12 +772,30 @@ module_naming_convention(Config, Target, RuleConfig) ->
Result = elvis_result:new(item, Msg, Info, 1),
[Result];
{match, _} ->
[]
case ForbiddenRegex of
undefined ->
[];
ForbiddenRegex ->
is_forbidden_module_name(ModuleNameStr,
ForbiddenRegex,
?FORBIDDEN_MODULE_NAMING_CONVENTION_MSG)
end
end;
true ->
[]
end.

is_forbidden_module_name(Target, Regex, Message) ->
case re:run(Target, Regex, [unicode]) of
{match, _} ->
Msg = Message,
Info = [Target, Regex],
Result = elvis_result:new(item, Msg, Info, 1),
[Result];
nomatch ->
[]
end.

-spec state_record_and_type(elvis_config:config(),
elvis_file:file(),
empty_rule_config()) ->
Expand Down Expand Up @@ -1118,10 +1169,19 @@ no_successive_maps(Config, Target, RuleConfig) ->
atom_naming_convention(Config, Target, RuleConfig) ->
Root = get_root(Config, Target, RuleConfig),
Regex = option(regex, RuleConfig, atom_naming_convention),
ForbiddenRegex = option(forbidden_regex, RuleConfig, atom_naming_convention),
RegexEnclosed =
specific_or_default(option(enclosed_atoms, RuleConfig, atom_naming_convention), Regex),
ForbiddenEnclosedRegex =
specific_or_default(option(forbidden_enclosed_regex, RuleConfig, atom_naming_convention),
ForbiddenRegex),
AtomNodes = elvis_code:find(fun is_atom_node/1, Root, #{traverse => all, mode => zipper}),
check_atom_names(Regex, RegexEnclosed, AtomNodes, []).
check_atom_names(Regex,
ForbiddenRegex,
RegexEnclosed,
ForbiddenEnclosedRegex,
AtomNodes,
[]).

-type no_init_lists_config() :: #{behaviours => [atom()]}.

Expand Down Expand Up @@ -1659,14 +1719,26 @@ is_exception_or_non_reversible(_) ->
false.

%% @private
check_atom_names(_Regex, _RegexEnclosed, [] = _AtomNodes, Acc) ->
check_atom_names(_Regex, _, _RegexEnclosed, _, [] = _AtomNodes, Acc) ->
Acc;
check_atom_names(Regex, RegexEnclosed, [AtomNode | RemainingAtomNodes], AccIn) ->
check_atom_names(Regex,
ForbiddenRegexNormal,
RegexEnclosed,
ForbiddenRegexEnclosed,
[AtomNode | RemainingAtomNodes],
AccIn) ->
AtomName0 = ktn_code:attr(text, AtomNode),
ValueAtomName = ktn_code:attr(value, AtomNode),
{IsEnclosed, AtomName} = string_strip_enclosed(AtomName0),
IsExceptionClass = is_exception_or_non_reversible(ValueAtomName),
RE = re_compile_for_atom_type(IsEnclosed, Regex, RegexEnclosed),
ForbiddenRegex =
case IsEnclosed of
true ->
ForbiddenRegexEnclosed;
false ->
ForbiddenRegexNormal
end,
AccOut =
case re:run(
unicode:characters_to_list(AtomName, unicode), RE)
Expand All @@ -1686,9 +1758,27 @@ check_atom_names(Regex, RegexEnclosed, [AtomNode | RemainingAtomNodes], AccIn) -
Result = elvis_result:new(item, Msg, Info, Line),
AccIn ++ [Result];
{match, _Captured} ->
AccIn
case ForbiddenRegex of
undefined ->
AccIn;
ForbiddenRegex ->
case re:run(AtomName, ForbiddenRegex, [unicode]) of
{match, _} ->
Msg = ?FORBIDDEN_ATOM_NAMING_CONVENTION_MSG,
Info = [AtomName, Regex],
Result = elvis_result:new(item, Msg, Info, 1),
AccIn ++ [Result];
nomatch ->
AccIn
end
end
end,
check_atom_names(Regex, RegexEnclosed, RemainingAtomNodes, AccOut).
check_atom_names(Regex,
ForbiddenRegexNormal,
RegexEnclosed,
ForbiddenRegexEnclosed,
RemainingAtomNodes,
AccOut).

%% @private
string_strip_enclosed([$' | Rest]) ->
Expand Down Expand Up @@ -1717,21 +1807,34 @@ is_atom_node(MaybeAtom) ->

%% Variables name
%% @private
check_variables_name(_Regex, []) ->
check_variables_name(_Regex, _, []) ->
[];
check_variables_name(Regex, [Variable | RemainingVars]) ->
check_variables_name(Regex, ForbiddenRegex, [Variable | RemainingVars]) ->
VariableNameStr = atom_to_list(ktn_code:attr(name, Variable)),
case re:run(VariableNameStr, Regex) of
nomatch when VariableNameStr == "_" ->
check_variables_name(Regex, RemainingVars);
check_variables_name(Regex, ForbiddenRegex, RemainingVars);
nomatch ->
Msg = ?VARIABLE_NAMING_CONVENTION_MSG,
{Line, _} = ktn_code:attr(location, Variable),
Info = [VariableNameStr, Line, Regex],
Result = elvis_result:new(item, Msg, Info, Line),
[Result | check_variables_name(Regex, RemainingVars)];
[Result | check_variables_name(Regex, ForbiddenRegex, RemainingVars)];
{match, _} ->
check_variables_name(Regex, RemainingVars)
case ForbiddenRegex of
undefined ->
check_variables_name(Regex, ForbiddenRegex, RemainingVars);
ForbiddenRegex ->
case re:run(VariableNameStr, ForbiddenRegex, [unicode]) of
{match, _} ->
Msg = ?FORBIDDEN_VARIABLE_NAMING_CONVENTION_MSG,
Info = [VariableNameStr, Regex],
Result = elvis_result:new(item, Msg, Info, 1),
[Result | check_variables_name(Regex, ForbiddenRegex, RemainingVars)];
nomatch ->
check_variables_name(Regex, ForbiddenRegex, RemainingVars)
end
end
end.

%% Result building
Expand Down
14 changes: 14 additions & 0 deletions test/examples/forbidden_atom_naming_convention.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-module(forbidden_atom_naming_convention).

-export([for_test/0]).

for_test() ->
this_is_an_ok_atom,
'and_so_is_this',
'and_this_1_too',
non_200,
'_', % used by ets/mnesia/etc.
non200, % valid, even without underscores
valid_200even_if_numb3rs_appear_between_letters,
blahblah_SUITE,
x.
Loading

0 comments on commit 2feea37

Please sign in to comment.