Skip to content

Ensure default groups for docs are sorted alphabetically #2132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/ex_doc/nodes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ defmodule ExDoc.DocNode do
end

defmodule ExDoc.DocGroupNode do
@moduledoc false
defstruct title: nil, description: nil, doc: nil, docs: []

@type t :: %__MODULE__{
Expand Down
3 changes: 2 additions & 1 deletion lib/ex_doc/retriever.ex
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,10 @@ defmodule ExDoc.Retriever do
defp get_docs_groups(module_groups, nodes_groups, doc_nodes) do
module_groups = Enum.map(module_groups, &normalize_group/1)

# Doc nodes already have normalized groups
nodes_groups_descriptions = Map.new(nodes_groups, &{&1.title, &1.description})

# Doc nodes already have normalized groups
nodes_groups = ExDoc.Utils.natural_sort_by(nodes_groups, & &1.title)
normal_groups = module_groups ++ nodes_groups
nodes_by_group_title = Enum.group_by(doc_nodes, & &1.group)

Expand Down
59 changes: 53 additions & 6 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,18 @@ defmodule Mix.Tasks.Docs do
### Grouping functions, types, and callbacks

Types, functions, and callbacks inside a module can also be organized in groups.
By default, ExDoc respects the `:group` metadata field:

#### Group metadata

By default, ExDoc respects the `:group` metadata field to dertermine in which
group an element belongs:

@doc group: "Queries"
def get_by(schema, fields)

The function above will be automatically listed under the "Queries" section in
the sidebar. The benefit of using `:group` is that it can also be used by tools
such as IEx during autocompletion. These groups are then ordered alphabetically
in the sidebar.
such as IEx during autocompletion. These groups are then displayed in the sidebar.

It is also possible to tell ExDoc to either enrich the group metadata or lookup a
different field via the `:default_group_for_doc` configuration. The default is:
Expand All @@ -322,9 +325,8 @@ defmodule Mix.Tasks.Docs do
end
end

Whenever using the `:group` key, the groups will be ordered alphabetically.
If you also want control over the group order, you can also use the `:groups_for_docs`
which works similarly as the one for modules/extra pages.
Finally, you can also use the `:groups_for_docs` which works similarly as the
one for modules/extra pages.

`:groups_for_docs` is a keyword list of group titles and filtering functions
that receive the documentation metadata and must return a boolean.
Expand Down Expand Up @@ -358,6 +360,51 @@ defmodule Mix.Tasks.Docs do
then falls back to the appropriate "Functions", "Types" or "Callbacks"
section respectively.

#### Group descriptions

It is possible to display a description for each group under its respective section
in a module's page. This helps to better explain what is the intended usage of each
group elements instead of describing everything in the displayed `@moduledoc`.

Descriptions can be provided as `@moduledoc` metadata. Groups without descriptions are
also supported to define group ordering.

@moduledoc groups: [
"Main API",
%{title: "Helpers", description: "Functions shared with other modules."}
]

Descriptions can also be given in the `:default_group_for_doc` configuration:

default_group_for_doc: fn metadata ->
csae metadata[:group] do
:main_api -> "Main API"
:helpers -> [title: "Helpers", description: "Functions shared with other modules."]
_ -> nil
end
end

Keyword lists or maps are supported in either case.

When using `:groups_for_docs`, if all the elements for a given group are matched then the
`:default_group_for_doc` is never invoked and ExDoc will not know about the description.
In that case, the description should be provided in the `@moduledoc` `:groups` metadata.

Whenever using the `:group` key, the groups will be ordered alphabetically.
If you also want control over the group order, you can also use the `:groups_for_docs`
which works similarly as the one for modules/extra pages.

#### Group ordering

Groups in the sidebar and main page body are ordered according to the following
rules:

* First, groups defined as `@moduledoc groups: [...]` in the given order.
* Then groups defined as keys in the `:groups_for_docs` configuration.
* Then default groups: Types, Callbacks and Functions.
* Finally, other groups returned by `:default_group_for_doc` by alphabetical
order.

## Meta-tags configuration

It is also possible to configure some of ExDoc behaviour using meta tags.
Expand Down
4 changes: 2 additions & 2 deletions test/ex_doc/retriever_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ defmodule ExDoc.RetrieverTest do

config = %ExDoc.Config{}
{[mod], []} = Retriever.docs_from_modules([A], config)
[%{docs: [bar]}, %{docs: [baz]}, %{docs: [foo]}] = mod.docs_groups
[%{docs: [foo]}, %{docs: [bar]}, %{docs: [baz]}] = mod.docs_groups

assert %{id: "c:foo/0", group: "a"} = foo
assert %{id: "bar/0", group: "b"} = bar
Expand All @@ -101,7 +101,7 @@ defmodule ExDoc.RetrieverTest do

config = %ExDoc.Config{group_for_doc: & &1[:semi_group]}
{[mod], []} = Retriever.docs_from_modules([A], config)
[%{docs: [bar]}, %{docs: [baz]}, %{docs: [foo]}] = mod.docs_groups
[%{docs: [foo]}, %{docs: [bar]}, %{docs: [baz]}] = mod.docs_groups

assert %{id: "c:foo/0", group: "a"} = foo
assert %{id: "bar/0", group: "b"} = bar
Expand Down