In 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

.gitignore rules

Project rules

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 .gitignore files.
  • 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_rule behaviour, which requires a default/1 function. It is called during config validation (see elvis_config.erl): when a rule is configured with options, only keys returned by default/1 (plus ignore) are accepted.
  • default(RuleName) must return a map of option names to default values for that rule. Use elvis_rule:defmap(#{option => default_value}) for each rule, and elvis_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 (use elvis_rule and elvis_file APIs to inspect it and the current file).
  • Argument 2: ElvisConfig :: elvis_config:t() — the config map for the current "file group" (one element of the config list in the configuration).
  • Return value: a list of elvis_result:item() — use elvis_result:new_item/3 for 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 for elvis_style-based rules only)
  • module, function and arity: ignore => [{mod, fun, arity}] (available for elvis_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}
].