diff --git a/lib/ex_doc/config.ex b/lib/ex_doc/config.ex index 670c14b75..a259edc1e 100644 --- a/lib/ex_doc/config.ex +++ b/lib/ex_doc/config.ex @@ -3,6 +3,7 @@ defmodule ExDoc.Config do # Defaults @default_source_ref "main" + def default_group_for_doc(metadata), do: metadata[:group] def filter_modules(_module, _metadata), do: true def before_closing_head_tag(_), do: "" def before_closing_footer_tag(_), do: "" @@ -21,6 +22,7 @@ defmodule ExDoc.Config do before_closing_head_tag: &__MODULE__.before_closing_head_tag/1, canonical: nil, cover: nil, + default_group_for_doc: &__MODULE__.default_group_for_doc/1, deps: [], extra_section: nil, extras: [], @@ -63,15 +65,16 @@ defmodule ExDoc.Config do before_closing_head_tag: (atom() -> String.t()) | mfa() | map(), canonical: nil | String.t(), cover: nil | Path.t(), + default_group_for_doc: (keyword() -> String.t() | nil), deps: [{ebin_path :: String.t(), doc_url :: String.t()}], extra_section: nil | String.t(), extras: list(), filter_modules: (module, map -> boolean), formatter: nil | String.t(), formatters: [String.t()], - groups_for_extras: keyword(), - groups_for_docs: keyword((keyword() -> boolean)), - groups_for_modules: keyword(), + groups_for_extras: [{binary(), term()}], + groups_for_docs: [{binary(), (keyword() -> boolean)}], + groups_for_modules: [{binary(), term()}], homepage_url: nil | String.t(), language: String.t(), logo: nil | Path.t(), @@ -96,7 +99,6 @@ defmodule ExDoc.Config do @spec build(String.t(), String.t(), Keyword.t()) :: ExDoc.Config.t() def build(project, vsn, options) do {output, options} = Keyword.pop(options, :output, "./doc") - {groups_for_modules, options} = Keyword.pop(options, :groups_for_modules, []) {nest_modules_by_prefix, options} = Keyword.pop(options, :nest_modules_by_prefix, []) {proglang, options} = Keyword.pop(options, :proglang, :elixir) {filter_modules, options} = Keyword.pop(options, :filter_modules, &filter_modules/2) @@ -109,6 +111,10 @@ defmodule ExDoc.Config do options end + {groups_for_docs, options} = Keyword.pop(options, :groups_for_docs, []) + {groups_for_extras, options} = Keyword.pop(options, :groups_for_extras, []) + {groups_for_modules, options} = Keyword.pop(options, :groups_for_modules, []) + {skip_undefined_reference_warnings_on, options} = Keyword.pop( options, @@ -126,7 +132,13 @@ defmodule ExDoc.Config do preconfig = %__MODULE__{ filter_modules: normalize_filter_modules(filter_modules), - groups_for_modules: normalize_groups_for_modules(groups_for_modules), + groups_for_docs: normalize_groups(groups_for_docs), + groups_for_extras: normalize_groups(groups_for_extras), + groups_for_modules: + normalize_groups( + # TODO: The default module groups must be returned by the language + groups_for_modules ++ [Deprecated: &deprecated?/1, Exceptions: &exception?/1] + ), homepage_url: options[:homepage_url], main: options[:main], nest_modules_by_prefix: normalize_nest_modules_by_prefix(nest_modules_by_prefix), @@ -159,12 +171,8 @@ defmodule ExDoc.Config do raise ArgumentError, "#{inspect(proglang)} is not supported" end - # TODO: The default module groups must be returned by the language - defp normalize_groups_for_modules(groups_for_modules) do - default_groups = [Deprecated: &deprecated?/1, Exceptions: &exception?/1] - - groups_for_modules ++ - Enum.reject(default_groups, fn {k, _} -> Keyword.has_key?(groups_for_modules, k) end) + defp normalize_groups(groups) do + for {k, v} <- groups, do: {to_string(k), v} end defp deprecated?(metadata), do: metadata[:deprecated] != nil diff --git a/lib/ex_doc/formatter/html.ex b/lib/ex_doc/formatter/html.ex index 1bbaacad2..2e7a32e61 100644 --- a/lib/ex_doc/formatter/html.ex +++ b/lib/ex_doc/formatter/html.ex @@ -383,7 +383,7 @@ defmodule ExDoc.Formatter.HTML do if ids_count[extra.id] > 1, do: {disambiguate_id(extra, idx), idx + 1}, else: {extra, idx} end) |> elem(0) - |> Enum.sort_by(fn extra -> GroupMatcher.group_index(groups, extra.group) end) + |> Enum.sort_by(fn extra -> GroupMatcher.index(groups, extra.group) end) end def generate_redirects(config, ext) do diff --git a/lib/ex_doc/formatter/html/templates.ex b/lib/ex_doc/formatter/html/templates.ex index d8b118385..fbd96c162 100644 --- a/lib/ex_doc/formatter/html/templates.ex +++ b/lib/ex_doc/formatter/html/templates.ex @@ -203,13 +203,12 @@ defmodule ExDoc.Formatter.HTML.Templates do end def module_summary(module_node) do - entries = docs_groups(module_node.docs_groups, module_node.docs ++ module_node.typespecs) - - Enum.reject(entries, fn {_type, nodes} -> nodes == [] end) - end - - defp docs_groups(groups, docs) do - for group <- groups, do: {group, Enum.filter(docs, &(&1.group == group))} + # TODO: Maybe it should be moved to retriever and it already returned grouped metadata + ExDoc.GroupMatcher.group_by( + module_node.docs_groups, + module_node.docs ++ module_node.typespecs, + & &1.group + ) end defp logo_path(%{logo: nil}), do: nil diff --git a/lib/ex_doc/group_matcher.ex b/lib/ex_doc/group_matcher.ex index 499fc02f5..ae021e42c 100644 --- a/lib/ex_doc/group_matcher.ex +++ b/lib/ex_doc/group_matcher.ex @@ -8,22 +8,38 @@ defmodule ExDoc.GroupMatcher do @doc """ Finds the index of a given group. """ - def group_index(groups, group) do + def index(groups, group) do Enum.find_index(groups, fn {k, _v} -> k == group end) || -1 end + @doc """ + Group the following entries and while preserving the order in `groups`. + """ + def group_by(groups, entries, by) do + entries = Enum.group_by(entries, by) + + {groups, leftovers} = + Enum.flat_map_reduce(groups, entries, fn group, grouped_nodes -> + case Map.pop(grouped_nodes, group, []) do + {[], grouped_nodes} -> {[], grouped_nodes} + {entries, grouped_nodes} -> {[{group, entries}], grouped_nodes} + end + end) + + groups ++ Enum.sort(leftovers) + end + @doc """ Finds a matching group for the given function. """ - @spec match_function(group_patterns, map) :: atom() | nil - def match_function(group_patterns, metadata) do - match_group_patterns(group_patterns, fn pattern -> pattern.(metadata) end) + def match_doc(group_patterns, callback, default, metadata) do + match_group_patterns(group_patterns, fn pattern -> pattern.(metadata) end) || + callback.(metadata) || default end @doc """ Finds a matching group for the given module name, id, and metadata. """ - @spec match_module(group_patterns, module, binary, map) :: atom() | nil def match_module(group_patterns, module, id, metadata) do match_group_patterns(group_patterns, fn pattern -> case pattern do @@ -38,7 +54,6 @@ defmodule ExDoc.GroupMatcher do @doc """ Finds a matching group for the given extra filename """ - @spec match_extra(group_patterns, binary) :: atom() | nil def match_extra(group_patterns, filename) do match_group_patterns(group_patterns, fn pattern -> case pattern do diff --git a/lib/ex_doc/language.ex b/lib/ex_doc/language.ex index eb84bee5f..0888052c8 100644 --- a/lib/ex_doc/language.ex +++ b/lib/ex_doc/language.ex @@ -49,10 +49,10 @@ defmodule ExDoc.Language do @doc """ Returns a map with module information. """ - @callback module_data(module(), tuple(), ExDoc.Config.t()) :: module_data() | :skip + @callback module_data(module(), tuple(), ExDoc.Config.t()) :: module_data() | false @doc """ - Returns a map with function information or an atom `:skip`. + Returns a map with function information or `false`. The map has the following keys: @@ -78,7 +78,7 @@ defmodule ExDoc.Language do doc_fallback: (... -> ExDoc.DocAST.t()) | nil, extra_annotations: [String.t()] } - | :skip + | false @doc """ Returns a map with callback information. diff --git a/lib/ex_doc/language/elixir.ex b/lib/ex_doc/language/elixir.ex index 0d79f6dab..c596adb03 100644 --- a/lib/ex_doc/language/elixir.ex +++ b/lib/ex_doc/language/elixir.ex @@ -8,7 +8,7 @@ defmodule ExDoc.Language.Elixir do @impl true @spec module_data(atom, any, any) :: - :skip + false | %{ callback_types: [:callback, ...], docs: any, @@ -34,7 +34,7 @@ defmodule ExDoc.Language.Elixir do cond do skip -> - :skip + false abst_code = Source.get_abstract_code(module) -> title = module_title(module, type) @@ -73,7 +73,7 @@ defmodule ExDoc.Language.Elixir do [] ) - :skip + false end end @@ -84,7 +84,7 @@ defmodule ExDoc.Language.Elixir do if doc?(entry, module_data.type) do function_data(kind, name, arity, anno, metadata, module_data) else - :skip + false end end diff --git a/lib/ex_doc/language/erlang.ex b/lib/ex_doc/language/erlang.ex index 946858711..f28878b86 100644 --- a/lib/ex_doc/language/erlang.ex +++ b/lib/ex_doc/language/erlang.ex @@ -8,7 +8,7 @@ defmodule ExDoc.Language.Erlang do @impl true @spec module_data(atom, any, any) :: - :skip + false | %{ callback_types: [:callback, ...], docs: any, @@ -54,7 +54,7 @@ defmodule ExDoc.Language.Erlang do } else ExDoc.Utils.warn("skipping docs for module #{inspect(module)}, reason: :no_debug_info", []) - :skip + false end end @@ -68,7 +68,7 @@ defmodule ExDoc.Language.Erlang do function_exported?(module_data.module, name, arity) do function_data(name, arity, doc_content, module_data, metadata) else - :skip + false end end diff --git a/lib/ex_doc/retriever.ex b/lib/ex_doc/retriever.ex index 76225a7b2..2649c7794 100644 --- a/lib/ex_doc/retriever.ex +++ b/lib/ex_doc/retriever.ex @@ -69,7 +69,7 @@ defmodule ExDoc.Retriever do defp sort_modules(modules, config) when is_list(modules) do Enum.sort_by(modules, fn module -> - {GroupMatcher.group_index(config.groups_for_modules, module.group), module.nested_context, + {GroupMatcher.index(config.groups_for_modules, module.group), module.nested_context, module.nested_title, module.id} end) end @@ -135,24 +135,18 @@ defmodule ExDoc.Retriever do {doc_line, doc_file, format, source_doc, doc, metadata} = get_module_docs(module_data, source) # TODO: The default function groups must be returned by the language - groups_for_docs = - config.groups_for_docs ++ - [ - Types: &(&1[:kind] in [:type, :opaque, :nominal]), - Callbacks: &(&1[:kind] in [:callback, :macrocallback]), - Functions: fn _ -> true end - ] - + default_group = config.default_group_for_doc + groups_for_docs = config.groups_for_docs annotations_for_docs = config.annotations_for_docs - docs_groups = Enum.map(groups_for_docs, &elem(&1, 0)) |> Enum.uniq() - function_docs = get_docs(module_data, source, groups_for_docs, annotations_for_docs) + docs_groups = + Enum.uniq(Enum.map(groups_for_docs, &elem(&1, 0)) ++ ~w(Types Callbacks Functions)) docs = - function_docs ++ - get_callbacks(module_data, source, groups_for_docs, annotations_for_docs) + get_docs(module_data, source, default_group, groups_for_docs, annotations_for_docs) ++ + get_callbacks(module_data, source, default_group, groups_for_docs, annotations_for_docs) - types = get_types(module_data, source, groups_for_docs, annotations_for_docs) + types = get_types(module_data, source, default_group, groups_for_docs, annotations_for_docs) metadata = Map.put(metadata, :kind, module_data.type) group = GroupMatcher.match_module(config.groups_for_modules, module, module_data.id, metadata) @@ -202,42 +196,37 @@ defmodule ExDoc.Retriever do ## Function helpers - defp get_docs(module_data, source, groups_for_docs, annotations_for_docs) do - {:docs_v1, _, _, _, _, _, doc_elements} = module_data.docs + defp get_docs(module_data, source, default_group, groups_for_docs, annotations_for_docs) do + {:docs_v1, _, _, _, _, _, docs} = module_data.docs nodes = - Enum.flat_map(doc_elements, fn doc_element -> - case module_data.language.function_data(doc_element, module_data) do - :skip -> - [] - - function_data -> - [ - get_function( - doc_element, - function_data, - source, - module_data, - groups_for_docs, - annotations_for_docs - ) - ] - end - end) + for doc <- docs, + function_data = module_data.language.function_data(doc, module_data) do + get_function( + doc, + function_data, + module_data, + source, + default_group, + groups_for_docs, + annotations_for_docs + ) + end filter_defaults(nodes) end defp get_function( - doc_element, + doc, function_data, - source, module_data, + source, + default_group, groups_for_docs, annotations_for_docs ) do {:docs_v1, _, _, content_type, _, module_metadata, _} = module_data.docs - {{type, name, arity}, anno, signature, source_doc, metadata} = doc_element + {{type, name, arity}, anno, signature, source_doc, metadata} = doc doc_file = anno_file(anno, source) doc_line = anno_line(anno) @@ -259,7 +248,7 @@ defmodule ExDoc.Retriever do (source_doc && doc_ast(content_type, source_doc, file: doc_file, line: doc_line + 1)) || function_data.doc_fallback.() - group = GroupMatcher.match_function(groups_for_docs, metadata) + group = GroupMatcher.match_doc(groups_for_docs, default_group, "Functions", metadata) %ExDoc.FunctionNode{ id: nil_or_name(name, arity), @@ -303,23 +292,31 @@ defmodule ExDoc.Retriever do defp get_callbacks( %{type: :behaviour} = module_data, source, + default_group, groups_for_docs, annotations_for_docs ) do {:docs_v1, _, _, _, _, _, docs} = module_data.docs for {{kind, _, _}, _, _, _, _} = doc <- docs, kind in module_data.callback_types do - get_callback(doc, source, groups_for_docs, module_data, annotations_for_docs) + get_callback(doc, module_data, source, default_group, groups_for_docs, annotations_for_docs) end end - defp get_callbacks(_, _, _, _), do: [] + defp get_callbacks(_, _, _, _, _), do: [] - defp get_callback(callback, source, groups_for_docs, module_data, annotations_for_docs) do - callback_data = module_data.language.callback_data(callback, module_data) + defp get_callback( + doc, + module_data, + source, + default_group, + groups_for_docs, + annotations_for_docs + ) do + callback_data = module_data.language.callback_data(doc, module_data) {:docs_v1, _, _, content_type, _, module_metadata, _} = module_data.docs - {{kind, name, arity}, anno, _signature, source_doc, metadata} = callback + {{kind, name, arity}, anno, _signature, source_doc, metadata} = doc doc_file = anno_file(anno, source) doc_line = anno_line(anno) @@ -342,11 +339,7 @@ defmodule ExDoc.Retriever do doc_ast(content_type, source_doc, file: doc_file, line: doc_line + 1) || doc_fallback(callback_data) - group = - GroupMatcher.match_function( - groups_for_docs, - metadata - ) + group = GroupMatcher.match_doc(groups_for_docs, default_group, "Callbacks", metadata) %ExDoc.FunctionNode{ id: "c:" <> nil_or_name(name, arity), @@ -368,21 +361,21 @@ defmodule ExDoc.Retriever do ## Typespecs - defp get_types(module_data, source, groups_for_docs, annotations_for_docs) do + defp get_types(module_data, source, default_group, groups_for_docs, annotations_for_docs) do {:docs_v1, _, _, _, _, _, docs} = module_data.docs for {{:type, _, _}, _, _, content, _} = doc <- docs, content != :hidden do - get_type(doc, source, groups_for_docs, module_data, annotations_for_docs) + get_type(doc, module_data, source, default_group, groups_for_docs, annotations_for_docs) end end - defp get_type(type_entry, source, groups_for_docs, module_data, annotations_for_docs) do + defp get_type(doc, module_data, source, default_group, groups_for_docs, annotations_for_docs) do {:docs_v1, _, _, content_type, _, module_metadata, _} = module_data.docs - {{kind, name, arity}, anno, _signature, source_doc, metadata} = type_entry + {{kind, name, arity}, anno, _signature, source_doc, metadata} = doc doc_file = anno_file(anno, source) doc_line = anno_line(anno) - type_data = module_data.language.type_data(type_entry, module_data) + type_data = module_data.language.type_data(doc, module_data) metadata = Map.merge( @@ -403,11 +396,7 @@ defmodule ExDoc.Retriever do doc_ast(content_type, source_doc, file: doc_file, line: doc_line + 1) || doc_fallback(type_data) - group = - GroupMatcher.match_function( - groups_for_docs, - metadata - ) + group = GroupMatcher.match_doc(groups_for_docs, default_group, "Types", metadata) %ExDoc.TypeNode{ id: "t:" <> nil_or_name(name, arity), diff --git a/lib/mix/tasks/docs.ex b/lib/mix/tasks/docs.ex index 3fb6b44ee..1c8d09d54 100644 --- a/lib/mix/tasks/docs.ex +++ b/lib/mix/tasks/docs.ex @@ -124,7 +124,8 @@ defmodule Mix.Tasks.Docs do * `:formatters` - Formatter to use; default: ["html", "epub"], options: "html", "epub". - * `:groups_for_extras`, `:groups_for_modules`, `:groups_for_docs` - See the "Groups" section + * `:groups_for_extras`, `:groups_for_modules`, `:groups_for_docs`, and `:default_group_for_doc` - + See the "Groups" section * `:ignore_apps` - Apps to be ignored when generating documentation in an umbrella project. Receives a list of atoms. Example: `[:first_app, :second_app]`. @@ -245,15 +246,41 @@ defmodule Mix.Tasks.Docs do A regex or the string name of the module is also supported. - ### Grouping functions and callbacks + ### Grouping functions, types, and callbacks - Functions and callbacks inside a module can also be organized in groups. - This is done via the `:groups_for_docs` configuration which is a - keyword list of group titles and filtering functions that receive the - documentation metadata of functions as argument. The metadata received will also - contain `:module`, `:name`, `:arity` and `:kind` to help identify which entity is - currently being processed. + Types, functions, and callbacks inside a module can also be organized in groups. + By default, ExDoc respects the `:group` metadata field: + @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. + + 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: + + default_group_for_doc: fn metadata -> metadata[:group] end + + The `metadata` received contains all of the documentation metadata, such as `:group`, + but also `:module`, `:name`, `:arity` and `:kind` to help identify which entity is + currently being processed. For example, projects like Nx have a custom function that + converts "Queries" into "Function: Queries": + + default_group_for_doc: fn metadata -> + if group = metadata[:group] do + "Functions: #{group}" + 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. + + `:groups_for_docs` is a keyword list of group titles and filtering functions + that receive the documentation metadata and must return a boolean. For example, imagine that you have an API client library with a large surface area for all the API endpoints you need to support. It would be helpful to group the functions with similar responsibilities together. In this case in @@ -278,10 +305,11 @@ defmodule Mix.Tasks.Docs do Admin: & &1[:permission] in [:grant, :write] ] - A function can belong to a single group only. If multiple group filters match, - the first will take precedence. Functions and callbacks that don't have a - custom group will be listed under the default "Functions" and "Callbacks" - group respectively. + A function can belong to a single group only. The first group that matches + will be the one used. In case no group is found in `:groups_for_docs`, + the `:default_group_for_doc` callback is invoked. If it returns nil, it + then falls back to the appropriate "Functions", "Types" or "Callbacks" + section respectively. ## Meta-tags configuration diff --git a/test/ex_doc/group_matcher_test.exs b/test/ex_doc/group_matcher_test.exs index eafb2b0f3..9f5832570 100644 --- a/test/ex_doc/group_matcher_test.exs +++ b/test/ex_doc/group_matcher_test.exs @@ -2,8 +2,18 @@ defmodule ExDoc.GroupMatcherTest do use ExUnit.Case, async: true import ExDoc.GroupMatcher + describe "group_by" do + test "group by given data with leftovers" do + assert group_by([1, 3, 5], [%{key: 1}, %{key: 3}, %{key: 2}], & &1.key) == [ + {1, [%{key: 1}]}, + {3, [%{key: 3}]}, + {2, [%{key: 2}]} + ] + end + end + describe "module matching" do - test "match modules by their atom names" do + test "by atom names" do patterns = [ Group: [MyApp.SomeModule, :lists] ] @@ -22,7 +32,7 @@ defmodule ExDoc.GroupMatcherTest do nil end - test "match modules by their string names" do + test "by string names" do patterns = [ Group: ["MyApp.SomeModule", ":lists"] ] @@ -36,7 +46,7 @@ defmodule ExDoc.GroupMatcherTest do nil end - test "match modules by regular expressions" do + test "by regular expressions" do patterns = [ Group: ~r/MyApp\..?/ ] @@ -53,7 +63,7 @@ defmodule ExDoc.GroupMatcherTest do end describe "extras matching" do - test "it can match extra files by their string names" do + test "by string names" do patterns = [ Group: ["docs/handling/testing.md"] ] @@ -62,7 +72,7 @@ defmodule ExDoc.GroupMatcherTest do assert match_extra(patterns, "docs/handling/setup.md") == nil end - test "it can match extra files by regular expressions" do + test "by regular expressions" do patterns = [ Group: ~r/docs\/handling?/ ] diff --git a/test/ex_doc/retriever/elixir_test.exs b/test/ex_doc/retriever/elixir_test.exs index ea7008594..34b508273 100644 --- a/test/ex_doc/retriever/elixir_test.exs +++ b/test/ex_doc/retriever/elixir_test.exs @@ -48,7 +48,7 @@ defmodule ExDoc.Retriever.ElixirTest do defaults: [], deprecated: nil, doc_line: 5, - group: :Functions, + group: "Functions", id: "function/0", name: :function, rendered_doc: nil, @@ -136,7 +136,7 @@ defmodule ExDoc.Retriever.ElixirTest do assert callback1.type == :callback assert callback1.annotations == [] assert callback1.doc_line == 2 - assert callback1.group == :Callbacks + assert callback1.group == "Callbacks" assert Path.basename(callback1.source_url) == "nofile:3" assert DocAST.to_string(callback1.doc) == "

callback1/0 docs.

" assert Macro.to_string(callback1.specs) == "[callback1() :: :ok]" @@ -146,7 +146,7 @@ defmodule ExDoc.Retriever.ElixirTest do assert optional_callback1.type == :callback assert optional_callback1.annotations == ["optional"] assert optional_callback1.doc_line == 5 - assert optional_callback1.group == :Callbacks + assert optional_callback1.group == "Callbacks" assert Path.basename(optional_callback1.source_url) == "nofile:5" refute optional_callback1.doc assert Macro.to_string(optional_callback1.specs) == "[optional_callback1() :: :ok]" @@ -156,7 +156,7 @@ defmodule ExDoc.Retriever.ElixirTest do assert macrocallback1.type == :macrocallback assert macrocallback1.annotations == [] assert macrocallback1.doc_line == 9 - assert macrocallback1.group == :Callbacks + assert macrocallback1.group == "Callbacks" assert Path.basename(macrocallback1.source_url) == "nofile:9" refute macrocallback1.doc assert Macro.to_string(macrocallback1.specs) == "[macrocallback1() :: :ok]" @@ -207,7 +207,7 @@ defmodule ExDoc.Retriever.ElixirTest do assert type1.id == "t:type1/0" assert type1.signature == "type1()" assert type1.type == :type - assert type1.group == :Types + assert type1.group == "Types" assert type1.annotations == [] assert type1.doc_line == 2 assert DocAST.to_string(type1.doc) == "

type1/0 docs.

" @@ -216,7 +216,7 @@ defmodule ExDoc.Retriever.ElixirTest do assert opaque1.id == "t:opaque1/0" assert opaque1.signature == "opaque1()" assert opaque1.type == :opaque - assert opaque1.group == :Types + assert opaque1.group == "Types" assert opaque1.doc_line == 5 assert opaque1.doc |> DocAST.to_string() == ~s|

opaque1/0 docs.

| assert opaque1.spec |> Macro.to_string() == "opaque1()" diff --git a/test/ex_doc/retriever/erlang_test.exs b/test/ex_doc/retriever/erlang_test.exs index 9220ddc6d..2b8d31496 100644 --- a/test/ex_doc/retriever/erlang_test.exs +++ b/test/ex_doc/retriever/erlang_test.exs @@ -55,7 +55,7 @@ defmodule ExDoc.Retriever.ErlangTest do moduledoc_line: 2, moduledoc_file: moduledoc_file, docs: [equiv_function2, function1, function2], - docs_groups: [:Types, :Callbacks, :Functions], + docs_groups: ["Types", "Callbacks", "Functions"], group: nil, id: "mod", language: ExDoc.Language.Erlang, @@ -80,7 +80,7 @@ defmodule ExDoc.Retriever.ErlangTest do deprecated: nil, doc_line: 5, doc_file: _, - group: :Functions, + group: "Functions", id: "function1/0", name: :function1, rendered_doc: nil, @@ -157,7 +157,7 @@ defmodule ExDoc.Retriever.ErlangTest do moduledoc_line: 6, moduledoc_file: moduledoc_file, docs: [callback, function], - docs_groups: [:Types, :Callbacks, :Functions], + docs_groups: ["Types", "Callbacks", "Functions"], group: nil, id: "mod", language: ExDoc.Language.Erlang, @@ -182,7 +182,7 @@ defmodule ExDoc.Retriever.ErlangTest do deprecated: nil, doc_line: 2, doc_file: _, - group: :Functions, + group: "Functions", id: "function/0", name: :function, rendered_doc: nil, @@ -251,7 +251,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert callback1.id == "c:callback1/0" assert callback1.type == :callback assert callback1.annotations == [] - assert callback1.group == :Callbacks + assert callback1.group == "Callbacks" assert DocAST.to_string(callback1.doc) =~ "callback1/0 docs." assert Path.basename(callback1.source_url) == "mod.erl:4" @@ -261,7 +261,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert equiv_callback1.id == "c:equiv_callback1/0" assert equiv_callback1.type == :callback assert equiv_callback1.annotations == [] - assert equiv_callback1.group == :Callbacks + assert equiv_callback1.group == "Callbacks" assert DocAST.to_string(equiv_callback1.doc) =~ ~r'Equivalent to ]+>callback1().*\.' @@ -270,7 +270,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert optional_callback1.id == "c:optional_callback1/0" assert optional_callback1.type == :callback - assert optional_callback1.group == :Callbacks + assert optional_callback1.group == "Callbacks" assert optional_callback1.annotations == ["optional"] end @@ -298,7 +298,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert opaque1.id == "t:opaque1/0" assert opaque1.type == :opaque - assert opaque1.group == :Types + assert opaque1.group == "Types" assert opaque1.signature == "opaque1()" assert opaque1.doc |> DocAST.to_string() =~ "opaque1/0 docs." @@ -307,7 +307,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert nominal1.id == "t:nominal1/0" assert nominal1.type == @nominal_type - assert nominal1.group == :Types + assert nominal1.group == "Types" assert nominal1.signature == "nominal1()" assert nominal1.doc |> DocAST.to_string() =~ "nominal1/0 docs." @@ -316,7 +316,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert type1.id == "t:type1/0" assert type1.type == :type - assert type1.group == :Types + assert type1.group == "Types" assert type1.signature == "type1()" assert type1.doc |> DocAST.to_string() =~ "type1/0 docs." @@ -325,7 +325,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert equiv_type1.id == "t:equiv_type1/0" assert equiv_type1.type == :type - assert equiv_type1.group == :Types + assert equiv_type1.group == "Types" assert equiv_type1.signature == "equiv_type1()" assert equiv_type1.doc |> DocAST.to_string() =~ ~r'Equivalent to .*t:type1/1.*\.' end @@ -401,7 +401,7 @@ defmodule ExDoc.Retriever.ErlangTest do deprecated: nil, moduledoc_line: _, docs: [function1, function2], - docs_groups: [:Types, :Callbacks, :Functions], + docs_groups: ["Types", "Callbacks", "Functions"], group: nil, id: "mod", language: ExDoc.Language.Erlang, @@ -425,7 +425,7 @@ defmodule ExDoc.Retriever.ErlangTest do deprecated: nil, doc_line: _, doc_file: _, - group: :Functions, + group: "Functions", id: "function1/0", name: :function1, rendered_doc: nil, @@ -523,7 +523,7 @@ defmodule ExDoc.Retriever.ErlangTest do assert nominal1.id == "t:nominal1/0" assert nominal1.type == @nominal_type - assert nominal1.group == :Types + assert nominal1.group == "Types" assert nominal1.signature == "nominal1/0" assert nominal1.doc |> DocAST.to_string() =~ "nominal1/0 docs." diff --git a/test/ex_doc/retriever_test.exs b/test/ex_doc/retriever_test.exs index 471c75fb0..7708d8889 100644 --- a/test/ex_doc/retriever_test.exs +++ b/test/ex_doc/retriever_test.exs @@ -50,15 +50,15 @@ defmodule ExDoc.RetrieverTest do config = %ExDoc.Config{ groups_for_modules: [ - "Group 1": [Foo, Bar], - "Group 2": [Baz] + {"Group 1", [Foo, Bar]}, + {"Group 2", [Baz]} ] } {[qux, bar, foo, baz], []} = Retriever.docs_from_modules([Foo, Bar, Baz, Qux], config) - assert %{module: Foo, group: :"Group 1"} = foo - assert %{module: Bar, group: :"Group 1"} = bar - assert %{module: Baz, group: :"Group 2"} = baz + assert %{module: Foo, group: "Group 1"} = foo + assert %{module: Bar, group: "Group 1"} = bar + assert %{module: Baz, group: "Group 2"} = baz assert %{module: Qux, group: nil} = qux end @@ -78,17 +78,63 @@ defmodule ExDoc.RetrieverTest do config = %ExDoc.Config{ groups_for_docs: [ - "Group 1": &(&1.group == 1), - "Group 2": &(&1.group == 2) + {"Group 1", &(&1.group == 1)}, + {"Group 2", &(&1.group == 2)} ] } {[mod], []} = Retriever.docs_from_modules([A], config) [bar, baz, foo] = mod.docs - assert %{id: "foo/0", group: :"Group 1"} = foo - assert %{id: "bar/0", group: :"Group 1"} = bar - assert %{id: "baz/0", group: :"Group 2"} = baz + assert %{id: "foo/0", group: "Group 1"} = foo + assert %{id: "bar/0", group: "Group 1"} = bar + assert %{id: "baz/0", group: "Group 2"} = baz + end + + test "function groups use :group metadata", c do + elixirc(c, ~S""" + defmodule A do + @doc group: "a" + @callback foo() :: :ok + + @doc group: "b" + def bar(), do: :ok + + @doc group: "c" + def baz(), do: :ok + end + """) + + config = %ExDoc.Config{} + {[mod], []} = Retriever.docs_from_modules([A], config) + [bar, baz, foo] = mod.docs + + assert %{id: "c:foo/0", group: "a"} = foo + assert %{id: "bar/0", group: "b"} = bar + assert %{id: "baz/0", group: "c"} = baz + end + + test "function groups use default_group_for_doc", c do + elixirc(c, ~S""" + defmodule A do + @doc semi_group: "a" + @callback foo() :: :ok + + @doc semi_group: "b" + def bar(), do: :ok + + @doc semi_group: "c" + def baz(), do: :ok + end + """) + + config = %ExDoc.Config{default_group_for_doc: & &1[:semi_group]} + {[mod], []} = Retriever.docs_from_modules([A], config) + [bar, baz, foo] = mod.docs + + assert %{id: "c:foo/0", group: "a"} = foo + assert %{id: "bar/0", group: "b"} = bar + assert %{id: "baz/0", group: "c"} = baz end test "function annotations", c do