Current section
Files
Jump to
Current section
Files
lib/mix/tasks/docs.check.ex
defmodule Mix.Tasks.Docs.Check do
use Mix.Task
@shortdoc "Fail when public Alloy modules/functions are missing docs"
@moduledoc """
Enforces documentation coverage for Alloy public API modules.
This task fails when:
- a public Alloy module is missing `@moduledoc` (unless `@moduledoc false`)
- a public function/macro is missing `@doc` (unless `@doc false`)
Intended for CI usage:
mix docs.check
"""
@impl Mix.Task
def run(_args) do
Mix.Task.run("compile")
modules = alloy_modules()
findings =
modules
|> Enum.flat_map(&module_findings/1)
|> Enum.sort()
if findings == [] do
Mix.shell().info("Docs check passed for #{length(modules)} Alloy modules.")
else
Mix.shell().error("Docs check failed with #{length(findings)} issue(s):")
Enum.each(findings, &Mix.shell().error(" - " <> &1))
Mix.raise("documentation coverage check failed")
end
end
defp alloy_modules do
app = Mix.Project.config()[:app]
case :application.load(app) do
:ok ->
:ok
{:error, {:already_loaded, ^app}} ->
:ok
{:error, reason} ->
Mix.raise("failed to load #{inspect(app)} application: #{inspect(reason)}")
end
app
|> Application.spec(:modules)
|> List.wrap()
|> Enum.filter(&public_alloy_module?/1)
end
defp public_alloy_module?(module) do
module
|> Atom.to_string()
|> String.starts_with?("Elixir.Alloy")
end
defp module_findings(module) do
case Code.fetch_docs(module) do
{:docs_v1, _anno, _language, _format, moduledoc, _metadata, docs} ->
missing_module =
if missing_doc?(moduledoc) do
["#{inspect(module)} missing @moduledoc (or @moduledoc false)"]
else
[]
end
missing_functions =
Enum.flat_map(docs, fn
{{kind, name, arity}, line, _signatures, doc, _meta}
when kind in [:function, :macro] ->
if missing_doc?(doc) do
["#{inspect(module)}.#{name}/#{arity} missing @doc (line #{line})"]
else
[]
end
_ ->
[]
end)
missing_module ++ missing_functions
{:error, reason} ->
["#{inspect(module)} has no docs chunk: #{inspect(reason)}"]
end
end
defp missing_doc?(:none), do: true
defp missing_doc?(nil), do: true
defp missing_doc?(_), do: false
end