# `SpecLint`
[🔗](https://github.com/lukaszsamson/spec_lint/blob/v0.1.0/lib/spec_lint.ex#L1)

Internal, unstable programmatic API. The supported public interfaces are
the Mix task, its configuration, and its JSON findings file.

Compares `@spec` declarations with the signatures the Elixir compiler
infers for the same functions.

The compiler stores an inferred signature for every function in the
`ExCk` chunk of the BEAM file. SpecLint translates each spec clause into
the compiler's types (`SpecLint.Typespec`), applies the inferred
signature to the spec's argument types with the compiler's own rule
(`apply_infer/2`), and compares the returns:

  * `return_conflict` (error): the inferred return is disjoint from the
    spec return, so no value the function returns satisfies the spec;
  * `domain_rejected` (error): no inferred clause accepts the spec's
    arguments, so the compiler would warn on every conforming call;
  * `clause_conflict` (warning): a clause that accepts only
    spec-conforming arguments returns a value disjoint from the spec
    return. A warning, not an error: the compiler keeps clauses that can
    never match (`def f(:b = x) when is_integer(x)`), so a stored clause
    is not proof of reachable behaviour;
  * `missing_return` (warning): the inferred return has a literal shape
    the spec does not declare, such as an `{:error, _}` tuple, an atom
    or a struct the spec never mentions;
  * `unexpected_return` (warning): the spec says `no_return()` and the
    compiler infers a concrete return.

Nothing else is reported: inference is deliberately conservative, so an
inferred type wider than the spec is normal and silent.

# `finding`

```elixir
@type finding() :: %{
  severity: :error | :warning,
  check: atom(),
  module: module(),
  function: atom(),
  arity: arity(),
  file: String.t() | nil,
  line: pos_integer() | nil,
  message: String.t(),
  spec: [String.t()],
  inferred: [String.t()]
}
```

One reported problem.

# `ignore`

```elixir
@type ignore() ::
  module() | {module(), atom()} | {module(), atom(), arity()} | Regex.t()
```

An ignore entry: a module, a `{module, function}` pair, a
`{module, function, arity}` triple, or a regex matched against
`"Module.function/arity"`.

# `ignored?`

```elixir
@spec ignored?(finding(), [ignore()]) :: boolean()
```

Whether `finding` matches one of the `ignore` entries.

# `mfa`

```elixir
@spec mfa(finding()) :: String.t()
```

`"Module.function/arity"` of a finding.

# `run`

```elixir
@spec run([Path.t()], keyword()) :: %{
  findings: [finding()],
  specs: non_neg_integer(),
  skipped: [{module(), atom(), arity(), term()}]
}
```

Analyses every `Elixir.*.beam` file in the `ebins` directories.

Options: `:ignore`, a list of `t:ignore/0` entries whose findings are
dropped; `:modules`, a list of modules to restrict the analysis to.
Returns the findings, the number of spec clauses compared and the spec
clauses skipped because a type could not be translated, with the reason.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
