View Source
Add fields docs to the @typedoc
For enhanced readability, itβs often beneficial to document the fields and parameters of a struct. Incorporating documentation can significantly aid both your co-workers and your future self by providing clear, accessible information.
Below is the final code generated by the plugin, which includes comprehensive
documentation for the User
struct:
defmodule User do
@typedoc """
@type t(age) :: %User{age: age | nil, name: String.t() | nil}
This is a user struct.
## Parameters
Name | Description
:age | The age parameter.
## Fields
Name | Type | Description
:name | String.t() | nil | The name of the user.
:age | age | nil | The age of the user.
"""
@type t(age) :: %__MODULE__{name: String.t() | nil, age: age | nil}
defstruct [:name, :age]
end
Implement
defmodule Guides.Plugins.DocFields do
@moduledoc """
The `DocFields` plugin generates documentation for fields and parameters.
Simply add the `:doc` option to the `field` and `parameter` macros to document them.
## Example
use TypedStructor
typed_structor do
@typedoc \"""
This is a user struct.
\"""
plugin Guides.Plugins.DocFields
parameter :age, doc: "The age parameter."
field :name, String.t(), doc: "The name of the user."
field :age, age, doc: "The age of the user."
end
This will generate the following documentation for you:
@typedoc \"""
This is a user struct.
## Parameters
| Name | Description |
|------|-------------|
|`:age` | The age parameter.|
## Fields
| Name | Type | Description |
|------|------|-------------|
|`:name` | `String.t() \| nil` | The name of the user.|
|`:age` | `age \| nil` | The age of the user.|
\"""
@type t(age) :: %User{age: age | nil, name: String.t() | nil}
"""
use TypedStructor.Plugin
@impl TypedStructor.Plugin
defmacro before_definition(definition, _opts) do
quote do
@typedoc unquote(__MODULE__).__generate_doc__(unquote(definition), @typedoc)
unquote(definition)
end
end
def __generate_doc__(_definition, false), do: nil
def __generate_doc__(definition, typedoc) do
parameters =
Enum.map(definition.parameters, fn parameter ->
name = Keyword.fetch!(parameter, :name)
doc = Keyword.get(parameter, :doc, "*not documented*")
["`#{inspect(name)}`", doc]
end)
parameters_docs =
if length(parameters) > 0 do
"""
## Parameters
| Name | Description |
|------|-------------|
#{join_rows(parameters)}
"""
end
fields =
Enum.map(definition.fields, fn field ->
name = Keyword.fetch!(field, :name)
type = Keyword.fetch!(field, :type)
type =
if Keyword.get(field, :enforce, false) or Keyword.has_key?(field, :default) do
Macro.to_string(type)
else
# escape `|`
"#{Macro.to_string(type)} \\| nil"
end
doc = Keyword.get(field, :doc, "*not documented*")
["`#{inspect(name)}`", "`#{type}`", doc]
end)
fields_docs =
if length(fields) > 0 do
"""
## Fields
| Name | Type | Description |
|------|------|-------------|
#{join_rows(fields)}
"""
end
[parameters_docs, fields_docs]
|> Enum.reject(&is_nil/1)
|> case do
[] ->
typedoc
docs ->
"""
#{typedoc}
#{Enum.join(docs, "\n\n")}
"""
end
end
defp join_rows(rows) do
Enum.map_join(rows, "\n", fn row -> "|" <> Enum.join(row, " | ") <> "|" end)
end
end
Usage
defmodule User do
@moduledoc false
use TypedStructor
typed_structor do
@typedoc """
This is a user struct.
"""
plugin Guides.Plugins.DocFields
parameter :age, doc: "The age parameter."
field :name, String.t(), doc: "The name of the user."
field :age, age, doc: "The age of the user."
end
end
iex> t User.t
@type t(age) :: %User{age: age | nil, name: String.t() | nil}
This is a user struct.
## Parameters
Name | Description
:age | The age parameter.
## Fields
Name | Type | Description
:name | String.t() | nil | The name of the user.
:age | age | nil | The age of the user.