Rules
View SourceIn this document, we present links to the rules supported by elvis and the available configuration
options for each one. At the end of this document, we share an
example elvis.config file you can copy-paste (in your project's root) and
then tweak to your liking.
Rules and other significant changes, like new options (starting from version 0.4.0), are
identified with
for convenience purposes.
Avoid, prefer, quick fix
Most, if not all, of the rules will present (opinionated) documentation sections titled "Avoid" and "Prefer". We aim to provide a "Rationale" with them and, in some cases, "Exceptions" or "Quick fix", if applicable.
Style rules
- Always Shortcircuit
- Atom Naming Convention
- Behaviour Spelling
- Consistent Ok/Error Spec
- Don't Repeat Yourself
- Export Used Types
- Expression Can Be Simplified
- Function Naming Convention
- Generic Type
- Guard Operators
- Include
ms_transformforets:fun2ms/1 - Macro Module Names - removed
- Macro Naming Convention
- Max Anonymous Function Arity
- Max Anonymous Function Clause Length
- Max Anonymous Function Length
- Max Function Arity
- Max Function Clause Length
- Max Function Length
- Max Line Length
- Max Module Length
- Max Record Fields
- Max Map Type Keys
- Module Naming Convention <!-- markdownlint-disable MD033 -->
- No <code>$ </code> <!-- markdownlint-enable MD033 -->
- No Author
- No Behavior Info
- No Block Expressions
- No Boolean In Comparison
- No Call
- No Catch Expressions
- No Common Caveats
- No Debug Call
- No Deep Nesting
- No God Modules
- No If Expression
- No Import
- No Invalid Dynamic Calls
- No Init Lists
- No Macros
- No Match In Condition
- No Includes
- No Nested
try...catchBlocks - No Operator With Same Values
- No Redundant Blank Lines
- No
receiveWithout Timeout - No Single-Clause Case Expressions
- No Single-Match Maybe Blocks
- No Space After
# - No Space
- No Spec With Records
- No Specs
- No Successive Maps
- No Tabs
- No Throw
- No Trailing Whitespace
- No Types
- No Used Ignored Variables
- Numeric Format
- Operator Spaces
- Param Pattern-Matching
- Prefer Include
- Prefer Robot Butt
- Prefer Sigils
- Prefer Strict Generators
- Prefer Unquoted Atoms
- Private Data Types
- Simplify Anonymous Functions
- State Record And Type
- Strict Term Equivalence
- Consistent Variable Naming
- Variable Naming Convention
- Parentheses in Macro Definitions
.gitignore rules
Project rules
- No Deps
mastererlang.mk- removed - No Deps
masterrebar.config- removed - No Deps With Branches
- Old Configuration Format - removed
- Protocol For Deps
erlang.mk- removed - Protocol For Deps
rebar.config- removed - Protocol For Deps
Rulesets
Rulesets in elvis are used to group individual rules together and can save a lot of duplication.
elvis currently has five pre-defined rulesets, but gives you the ability to specify custom
rulesets in the configuration file.
The six pre-defined rulesets are:
erl_files, for Erlang source files (pre-defined ruleset).erl_files_test, for Erlang test files (pre-defined ruleset).erl_files_strict, for Erlang source files (all applicable rules).gitignore, for.gitignorefiles.hrl_files, for Erlang header files (pre-defined ruleset).hrl_files_strict, for Erlang header files (all applicable rules).rebar_config, for rebar configuration files.
Custom rulesets are defined in a {RuleNamespace, #{}} tuple in elvis' configuration. Each key in
the map represents the ruleset name and is mapped to a list of rules as otherwise defined in a
standard ruleset.
Example configuration with a custom ruleset (named my_ruleset):
[
{rulesets, #{
my_ruleset => [
{elvis_style, max_module_length, #{}},
{elvis_style, no_common_caveats_call, #{}}
]
}},
{config, [
#{
dirs => ["src/**", "test/**"],
filter => "*.erl",
ruleset => my_ruleset
}
]}
].User-defined rules
You can define your own rules by implementing a module that follows the interface below.
Module requirements
- Your module must implement the
elvis_rulebehaviour, which requires adefault/1function. It is called during config validation (seeelvis_config.erl): when a rule is configured with options, only keys returned bydefault/1(plusignore) are accepted. default(RuleName)must return a map of option names to default values for that rule. Useelvis_rule:defmap(#{option => default_value})for each rule, andelvis_rule:defmap(#{})as a catch-all for unknown rule names.
Rule function signature
Each rule is implemented by a function of the following signature:
-spec FunctionName(elvis_rule:t(), elvis_config:t()) -> [elvis_result:item()].- Argument 1:
Rule :: elvis_rule:t()— the opaque rule term (useelvis_ruleandelvis_fileAPIs to inspect it and the current file). - Argument 2:
ElvisConfig :: elvis_config:t()— the config map for the current "file group" (one element of theconfiglist in the configuration). - Return value: a list of
elvis_result:item()— useelvis_result:new_item/3for each violation. Return[]when the file passes the rule.
Configuration
Add your rule to the rules list in a config section (or in a custom ruleset referenced by
ruleset):
{MyModule, my_rule}— rule with default options{MyModule, my_rule, #{opt => val}}— rule with overridden options
Complete example: no_todo_comments
A minimal rule that reports any line containing TODO (or FIXME) in a comment or string:
-module(my_rules).
-behaviour(elvis_rule).
-export([default/1, no_todo_comments/2]).
default(no_todo_comments) ->
elvis_rule:defmap(#{ignore => []});
default(_) ->
elvis_rule:defmap(#{}).
no_todo_comments(Rule, ElvisConfig) ->
Ignore = elvis_rule:option(ignore, Rule),
File = elvis_rule:file(Rule),
Path = elvis_file:path(File),
{Src, _} = elvis_file:src(File),
case lists:member(filename:basename(Path, ".erl"), [atom_to_list(M) || M <- Ignore]) of
true -> [];
false ->
Lines = binary:split(Src, <<"\n">>, [global]),
Items = lists:flatmap(
fun({LineNum, Line}) ->
case binary:match(Line, <<"TODO">>) =/= nomatch
orelse binary:match(Line, <<"FIXME">>) =/= nomatch of
true ->
[
elvis_result:new_item(
"TODO/FIXME found at line ~p",
[LineNum],
#{line => LineNum}
)
];
false -> []
end
end,
lists:zip(lists:seq(1, length(Lines)), Lines)
),
Items
end.And in elvis.config:
[
{config, [
#{
dirs => ["src"],
filter => "*.erl",
ruleset => erl_files,
rules => [
{elvis_style, no_debug_call, #{ignore => [my_mod]}},
{my_rules, no_todo_comments, #{ignore => [my_rules]}}
]
}
]}
].The -elvis attribute
Per-module rules can also be configured using attribute -elvis(_)., with the same content as is
expected in elvis.config's rules option, e.g.:
-elvis([{elvis_style, no_behavior_info, #{}}]).
-elvis([{elvis_style, no_nested_try_catch}]).Note: a single attribute with a list of rules is the same as multiple attributes with a list of rules each - the rules are "merged" - as in:
-elvis([{elvis_style, no_behavior_info, #{}}, {elvis_style, no_nested_try_catch}]).In this case, the ignore attribute has limited value since it'll be ignored for "other" modules.
You can always play with the following, but results may not be surprising.
-module(mymodule).
-elvis([{elvis_style, no_deep_nesting, #{ level => 4, ignore => [mymodule] }}]).
...Disabling rules
Rules (as used by you, in elvis.config) come in 3-element tuples (if you use options) or 2-element
tuples (if you don't). To disable a rule, you need to use the 3-element form, and the reserved word
disable: let's consider you want to disable rule elvis_text_style:no_tabs; you do
{elvis_text_style, no_tabs, disable}, and you're done!
The ignore option
Module-level rules implement a generic ignore mechanism that allows skipping analysis in elements of
your choice.
It suffices to add the ignore list to your rules, as per the example below.
-elvis([{elvis_style, no_invalid_dynamic_calls, #{ ignore => [elvis_core]}}]).
-elvis([{elvis_style, no_debug_call, #{ ignore => [elvis_result, elvis_utils]}}]).You can add the exceptions using the following syntax:
- file name:
ignore => ["path/to/header.hrl"] - whole module:
ignore => [mod] - functions (by name only):
ignore => [{mod, fun}](available forelvis_style-based rules only) - module, function and arity:
ignore => [{mod, fun, arity}](available forelvis_style-based rules only) - wildcard patterns: use the atom
'_'in any position to match any module, function, or arity, e.g.ignore => [{mod, '_'}](all functions in a module),ignore => [{'_', fun}](a function in any module),ignore => [{'_', '_'}](ignore everything),ignore => [{mod, fun, '_'}](ignore a function in a module for all arities)
BEAM files
Specific rules (signaled with ) allow you to
perform analysis directly on beam files (instead of source code).
To target these, specifiy beam_files (or beam_files_strict), instead of erl_files (or
erl_files_strict), as the target ruleset.
Though this analysis may be useful for pin-pointing certain elements, beware that, e.g., reported line numbers will most surely not correspond with those in the source file.
HRL files
Specific rules (signaled with ) are reserved
for
.hrl files.
Example elvis.config
[
{config, [
#{
dirs => ["src/**", "test/**"],
filter => "*.erl",
ruleset => erl_files,
% these are not enforced by default, so are added here for completeness
rules => [
{elvis_style, max_module_length, #{}},
{elvis_style, no_common_caveats_call, #{}}
]
},
#{
dirs => ["include/**"],
filter => "*.hrl",
ruleset => hrl_files
},
#{
dirs => ["."],
filter => "rebar.config",
ruleset => rebar_config,
rules => []
},
#{
dirs => ["."],
filter => ".gitignore",
ruleset => gitignore
}
]},
{verbose, true}
].