Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #162: New Rule: No Types #270

Merged
merged 1 commit into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ identified with `(since ...)` for convenience purposes.
- [No Tabs](doc_rules/elvis_text_style/no_tabs.md)
- [No throw](doc_rules/elvis_style/no_throw.md)
- [No Trailing Whitespace](doc_rules/elvis_text_style/no_trailing_whitespace.md)
- [No Types](doc_rules/elvis_style/no_types.md)
- [Numeric Format](doc_rules/elvis_style/numeric_format.md)
- [Operator Spaces](doc_rules/elvis_style/operator_spaces.md)
- [State Record and Type](doc_rules/elvis_style/state_record_and_type.md)
Expand All @@ -63,10 +64,10 @@ identified with `(since ...)` for convenience purposes.
## Rulesets

Rulesets in `elvis` are used to group individual rules together and can save a lot of duplication.
`elvis` currently has four pre-defined rulesets, but gives you the ability to specify custom
`elvis` currently has five pre-defined rulesets, but gives you the ability to specify custom
rulesets in the configuration file.

The four pre-defined rulesets are:
The five pre-defined rulesets are:

- `hrl_files`, for Erlang header files.
- `erl_files`, for Erlang source files.
Expand Down
2 changes: 1 addition & 1 deletion config/elvis-test-hrl-files.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[{elvis,
[{config,
[#{dirs => ["../../_build/test/lib/elvis_core/test/examples/"],
filter => "*.hrl",
filter => "test-*.hrl",
ruleset => hrl_files}]}]}].
26 changes: 26 additions & 0 deletions doc_rules/elvis_style/no_types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# No Types

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

Avoid `-type` attributes.

This rule is meant to be used on header files only.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can be restricted by code, right? Or does Erlang accept, for header files, anything other than .hrl?

Copy link
Member Author

@elbrujohalcon elbrujohalcon Feb 27, 2023

Choose a reason for hiding this comment

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

Erlang accepts anything. You can 100% write this and it will work…

-include("LICENSE.txt").
-module my_mod.

…

Why on Earth will you want to do that is a mystery to me, but it's 100% valid.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant when we're listing the files for analysis we can restrict certain rules to certain file types (not sure if desired).

Copy link
Collaborator

Choose a reason for hiding this comment

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

This rule is meant to be used on header files only.

But wait, our analysis already is scoped, right? If you use a rebar.config rule on an .erl file it shouldn't work, should it? Not sure I've tried it or we test it, but pretty sure there's some restrictions in place.

Defining types in public header files (especially those intended for inclusion via -include_lib())
might lead to type name clashes between projects and even modules of a single big project.
Instead, types should be defined in the modules which they correspond to
(using `-export_type()` appropriately) and, in this way, take advantage of the namespacing offered
by module names.
In other words, this rule means that we will always need to use `some_mod:some_type()` unless
we're referring to a type defined in the same module.

> Works on `.beam` file? Yes, but it's not useful there. This rule is meant to be used for header files.

## Options

- None.

## Example

```erlang
{elvis_style, no_types}
```
3 changes: 2 additions & 1 deletion src/elvis_rulesets.erl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ rules(hrl_files) ->
{elvis_style, no_dollar_space},
{elvis_style, no_author},
{elvis_style, no_catch_expressions},
{elvis_style, numeric_format}]);
{elvis_style, numeric_format},
{elvis_style, no_types}]);
rules(erl_files) ->
lists:map(fun({Mod, Rule}) -> {Mod, Rule, apply(Mod, default, [Rule])} end,
[{elvis_text_style, line_length},
Expand Down
24 changes: 23 additions & 1 deletion src/elvis_style.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-module(elvis_style).

-export([default/1, function_naming_convention/3, variable_naming_convention/3,
macro_names/3, macro_module_names/3, no_macros/3, no_block_expressions/3,
macro_names/3, macro_module_names/3, no_macros/3, no_types/3, no_block_expressions/3,
operator_spaces/3, no_space/3, nesting_level/3, god_modules/3, no_if_expression/3,
invalid_dynamic_call/3, used_ignored_variable/3, no_behavior_info/3,
module_naming_convention/3, state_record_and_type/3, no_spec_with_records/3,
Expand All @@ -23,6 +23,7 @@
-define(MACRO_AS_FUNCTION_NAME_MSG,
"Don't use macros (like ~s on line ~p) as function names.").
-define(NO_MACROS_MSG, "Unexpected macro (~p) used on line ~p.").
-define(NO_TYPES_MSG, "Unexpected type (~p) defined on line ~p.").
-define(NO_BLOCK_EXPRESSIONS_MSG,
"Unexpected block expression (begin-end) used on line ~p.").
-define(MISSING_SPACE_MSG, "Missing space to the ~s of ~p on line ~p").
Expand Down Expand Up @@ -167,6 +168,7 @@ default(consistent_generic_type) ->
default(RuleWithEmptyDefault)
when RuleWithEmptyDefault == macro_module_names;
RuleWithEmptyDefault == no_macros;
RuleWithEmptyDefault == no_types;
RuleWithEmptyDefault == no_block_expressions;
RuleWithEmptyDefault == no_if_expression;
RuleWithEmptyDefault == no_nested_try_catch;
Expand Down Expand Up @@ -321,6 +323,26 @@ no_macros(ElvisConfig, RuleTarget, RuleConfig) ->
is_macro_node(Node) ->
ktn_code:type(Node) =:= macro.

-type no_types_config() :: #{allow => [atom()], ignore => [ignorable()]}.

-spec no_types(elvis_config:config(), elvis_file:file(), no_types_config()) ->
[elvis_result:item()].
no_types(ElvisConfig, RuleTarget, RuleConfig) ->
TreeRootNode = get_root(ElvisConfig, RuleTarget, RuleConfig),
TypeNodes =
elvis_code:find(fun is_type_attribute/1, TreeRootNode, #{traverse => all, mode => node}),

lists:foldl(fun(TypeNode, Acc) ->
Type = ktn_code:attr(name, TypeNode),
{Line, _Col} = ktn_code:attr(location, TypeNode),
[elvis_result:new(item, ?NO_TYPES_MSG, [Type, Line], Line) | Acc]
end,
[],
TypeNodes).

is_type_attribute(Node) ->
ktn_code:type(Node) =:= type_attr.

-type no_block_expressions_config() :: #{ignore => [ignorable()]}.

-spec no_block_expressions(elvis_config:config(),
Expand Down
4 changes: 2 additions & 2 deletions test/elvis_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ hrl_ruleset(_Config) ->
ConfigPath = "../../config/elvis-test-hrl-files.config",
ElvisConfig = elvis_config:from_file(ConfigPath),
{fail,
[#{file := "../../_build/test/lib/elvis_core/test/examples/good.hrl", rules := []},
#{file := "../../_build/test/lib/elvis_core/test/examples/bad.hrl",
[#{file := "../../_build/test/lib/elvis_core/test/examples/test-good.hrl", rules := []},
#{file := "../../_build/test/lib/elvis_core/test/examples/test-bad.hrl",
rules := [#{name := line_length}]}]} =
elvis_core:rock(ElvisConfig),
ok.
Expand Down
1 change: 1 addition & 0 deletions test/examples/fail_no_types.hrl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-type bad_type() :: no | types | should | be | defined | in | header | files.
6 changes: 4 additions & 2 deletions test/examples/hrl_usage.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
-module hrl_usage.

-include("good.hrl").
-include("bad.hrl").
-include("test-good.hrl").
-include("test-bad.hrl").
-include("fail_no_types.hrl").
-include("pass_no_types.hrl").
6 changes: 4 additions & 2 deletions test/examples/more_hrl_usage.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
-module more_hrl_usage.

-include("good.hrl").
-include("bad.hrl").
-include("test-good.hrl").
-include("test-bad.hrl").
-include("fail_no_types.hrl").
-include("pass_no_types.hrl").
1 change: 1 addition & 0 deletions test/examples/pass_no_types.hrl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-define(THIS_FILE, {has, no, types}).
File renamed without changes.
File renamed without changes.
10 changes: 9 additions & 1 deletion test/style_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
verify_atom_naming_convention/1, verify_no_throw/1, verify_no_dollar_space/1,
verify_no_author/1, verify_no_catch_expressions/1, verify_numeric_format/1,
verify_behaviour_spelling/1, verify_always_shortcircuit/1,
verify_consistent_generic_type/1]).
verify_consistent_generic_type/1, verify_no_types/1]).
%% -elvis attribute
-export([verify_elvis_attr_atom_naming_convention/1, verify_elvis_attr_numeric_format/1,
verify_elvis_attr_dont_repeat_yourself/1, verify_elvis_attr_function_naming_convention/1,
Expand Down Expand Up @@ -381,6 +381,14 @@ verify_no_macros(Config) ->
#{allow => ['ALLOWED_MACRO']},
PathPass).

-spec verify_no_types(config()) -> any().
verify_no_types(Config) ->
PathFail = "fail_no_types.hrl",
[#{line_num := 1}] = elvis_core_apply_rule(Config, elvis_style, no_types, #{}, PathFail),

PathPass = "pass_no_types.hrl",
[] = elvis_core_apply_rule(Config, elvis_style, no_types, #{}, PathPass).

-spec verify_no_block_expressions(config()) -> any().
verify_no_block_expressions(Config) ->
Ext = proplists:get_value(test_file_ext, Config, "erl"),
Expand Down