ExQuality.Stages.DocLinks (ExQuality v0.15.0)

Copy Markdown View Source

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

Summary

Functions

Runs the doc links stage.

Functions

run(config)

@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)