Packages

Credo checks for the anti-patterns described in the official Elixir documentation.

Current section

Files

Jump to
smell README.md
Raw

README.md

# Smell

[Credo](https://github.com/rrrene/credo) checks for the
[anti-patterns described in the official Elixir documentation](https://hexdocs.pm/elixir/what-anti-patterns.html).

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`:

```elixir
def deps do
  [
    {:smell, "~> 0.2", only: [:dev, :test], runtime: false}
  ]
end
```

Then enable the plugin in your `.credo.exs`:

```elixir
%{
  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:

```elixir
checks: %{
  enabled:
    [
      # ...your existing checks...
    ] ++ Enum.map(Smell.checks(), &{&1, []})
}
```

## Checks

| Check | Anti-pattern | Reports |
| --- | --- | --- |
| `Smell.Check.Design.AppConfigInLibrary` | [Using application configuration for libraries](https://hexdocs.pm/elixir/design-anti-patterns.html#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`](https://hexdocs.pm/elixir/macro-anti-patterns.html#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`](https://hexdocs.pm/elixir/code-anti-patterns.html#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](https://hexdocs.pm/elixir/code-anti-patterns.html#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](https://hexdocs.pm/elixir/code-anti-patterns.html#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`): `:auto` checks the project when its `mix.exs` has
  a `:package` and no `:releases`; `true` always checks it, `false` never 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` (default `true`): skip files under `test/`.

```elixir
{Smell.Check.Design.AppConfigInLibrary, [allowed_keys: [:statuses]]}
```

`UseInsteadOfImport`:

- `exclude_test_files` (default `true`): skip files under `test/`.

`ComplexElseClauses`:

- `max_else_clauses` (default `1`): the maximum number of clauses allowed in the
  `else` block.

`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` (default `true`): skip files under `test/`.

```elixir
{Smell.Check.Warning.NamespaceTrespassing, [allowed_namespaces: [Phoenix.LiveView]]}
```

`NonAssertivePatternMatching`:

- `exclude_test_files` (default `true`): skip files under `test/`.

### 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](https://github.com/RicardoSantos-99/smell/blob/master/LICENSE).