# `ExQuality.Stages.DocLinks`
[🔗](https://github.com/riddler/ex_quality/blob/v0.15.0/lib/ex_quality/stages/doc_links.ex#L1)

Checks the relative links in a package's published Markdown against the
places they are published, where ExDoc mostly accepts a broken one silently.

ExDoc rewrites a relative `.md` link to the matching `.html` page only when
the target is itself one of the project's `extras`, and it looks the target
up by basename alone. Every other relative link stays a raw `href`: it works
on GitHub and answers 404 on HexDocs. ExDoc warns that a Markdown, `.txt` or
extension-less target "does not exist" (even when it is on disk) and says
nothing about any other target, such as `mix.exs` or a source file. A link
whose basename happens to match some other extra is rewritten to *that*
extra without a word, so a link to `docs/adr/README.md` lands on the
package's front page. The hex.pm package page renders the README from the
package tarball, so a relative path in the README resolves against the
files the package ships and answers 404 there unless the file is among
them.

    ✓ Doc links: 42 links checked (0.1s)
    ✗ Doc links: 3 problems (0.1s)

Each problem is a finding at the `file:line` of the link:

    docs/holds.md
      12  [error] links to docs/adr/0001-holds.md, which is not in extras; HexDocs answers 404 for it (not_an_extra)

## Rules

- `readme_not_packaged` - a relative link or image in `README.md` whose
  target is not covered by the package's files. When `package` names no
  `files:`, Hex's default list applies (`lib`, `priv`, `.formatter.exs`,
  `mix.exs`, `README*`, `readme*`, `LICENSE*`, `license*`, `CHANGELOG*`,
  `changelog*`, `src`, `c_src`, `Makefile*`), and the stage checks against
  that rather than reporting nothing.
- `not_an_extra` - a relative link in a Markdown extra whose target is not
  itself an extra. A link into a directory the docs config copies with
  `assets:` is not one: ExDoc publishes those files as they are.
- `duplicate_extra` - two extras that share a basename, with no `filename:`
  on the second. The finding is at the second one's line in `mix.exs`.
- `rewritten_to_other_extra` - a relative link ExDoc would rewrite to a
  different extra than the file it names, because the basename matches that
  extra and ExDoc resolves a basename to the last extra declared with it.

Absolute URLs, `mailto:` and ExDoc's own `e:`, `m:` and backticked forms,
anchors and absolute paths are ignored; an anchor or query on a relative link
is stripped before the check. Links inside code spans and fenced code blocks
are not links. Images are checked only in the README, whose preview is the
one place a missing image is not the docs config's business (`assets:`).

Links in moduledocs and function docs are the Docs stage's
(`ExQuality.Stages.Docs`): ExDoc warns on those.

## What it reads

The project's own config, not the built output: `extras` from the `docs`
config (a keyword list, or a zero-arity function returning one, as ExDoc
accepts; each extra a path or a `{path, opts}` pair) and `files` from the
`package` config. It builds nothing and runs no tool, so it takes well under
a second.

## Opt-in

Like the Docs stage this one is **off by default**, even when `:ex_doc` is
installed: enabling it on detection would turn currently-green gates red on
upgrade, and this project does not move anyone's gate by default. Enable it
in `.quality.exs`:

    doc_links: [enabled: :auto]   # on when :ex_doc is installed (recommended)
    doc_links: [enabled: true]    # forced, with or without :ex_doc

# `run`

```elixir
@spec run(keyword()) :: ExQuality.Stage.result()
```

Runs the doc links stage.

## Config options

- `enabled` - `false` (default) | `:auto` (on when `:ex_doc` is installed) |
  `true` (forced)

---

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