Smell
Credo checks for the anti-patterns described in the official Elixir documentation.
The catalog comes from the research of Lucas Vegi and Marco Tulio Valente (ASERG/DCC/UFMG). Smell turns the anti-patterns that can be found by static analysis into Credo checks, and links each check to the matching section of the documentation.
Installation
Add smell to your dependencies in mix.exs:
def deps do
[
{:smell, "~> 0.2", only: [:dev, :test], runtime: false}
]
end
Then enable the plugin in your .credo.exs:
%{
configs: [
%{
name: "default",
plugins: [{Smell, []}]
}
]
}
If your .credo.exs declares an explicit checks: %{enabled: [...]} list (as
the one generated by mix credo.gen.config does), Credo only runs the checks in
that list. Add Smell's checks to it:
checks: %{
enabled:
[
# ...your existing checks...
] ++ Enum.map(Smell.checks(), &{&1, []})
}
Checks
| Check | Anti-pattern | Reports |
|---|---|---|
Smell.Check.Design.AppConfigInLibrary |
Using application configuration for libraries | In a library (a mix.exs with :package and no :releases), a read of the library's own application environment (Application.get_env/3, fetch_env!/2, compile_env/3, ...), except keys that swap a component (:adapter, :json_library, ...) and the :otp_app pattern |
Smell.Check.Design.UseInsteadOfImport |
use instead of import |
A __using__/1 whose clauses only inject import, alias and require directives (unless it is deprecated) |
Smell.Check.Readability.ComplexElseClauses |
Complex else clauses in with |
A with with two or more <- clauses whose else block has more than max_else_clauses (default 1) clauses |
Smell.Check.Warning.NamespaceTrespassing |
Namespace trespassing | A top-level module defined inside a namespace owned by Elixir (Mix, Logger, ...) or by a dependency listed in mix.lock (Plug, Phoenix, ...) |
Smell.Check.Warning.NonAssertivePatternMatching |
Non-assertive pattern matching | A case that matches {:ok, value} and then everything else with _, with no clause for :error or {:error, ...} |
Run mix credo explain on an issue to see the full explanation and the
parameters of each check.
Parameters
AppConfigInLibrary:
library(default:auto)::autochecks the project when itsmix.exshas a:packageand no:releases;truealways checks it,falsenever does.allowed_keys(default[]): keys the library may read, as atoms or regexes, for example settings used to generate code from data files:[:statuses, ~r/^debug_/]. For a key path such as[MyLib.Repo, :timeout], the last key is matched.exclude_test_files(defaulttrue): skip files undertest/.
{Smell.Check.Design.AppConfigInLibrary, [allowed_keys: [:statuses]]}
UseInsteadOfImport:
exclude_test_files(defaulttrue): skip files undertest/.
ComplexElseClauses:
max_else_clauses(default1): the maximum number of clauses allowed in theelseblock.
NamespaceTrespassing:
allowed_namespaces(default[]): namespaces the project may define modules in, for example when you maintain both packages:[Phoenix.LiveView].lockfile(default"mix.lock"): where to read dependencies from, relative to where Credo runs.exclude_test_files(defaulttrue): skip files undertest/.
{Smell.Check.Warning.NamespaceTrespassing, [allowed_namespaces: [Phoenix.LiveView]]}
NonAssertivePatternMatching:
exclude_test_files(defaulttrue): skip files undertest/.
Anti-patterns Credo already covers
Some anti-patterns from the documentation already have a check in Credo itself, so Smell does not duplicate them:
| Anti-pattern | Credo check |
|---|---|
| Dynamic atom creation | Credo.Check.Warning.UnsafeToAtom |
| Long parameter list | Credo.Check.Refactor.FunctionArity |
| Structs with 32 fields or more | Credo.Check.Warning.StructFieldAmount (Credo 1.7.14+) |
with with a single <- clause and an else |
Credo.Check.Readability.WithSingleClause |
License
MIT. See LICENSE.