From b943950d2597d927fb293f9e17c7f0d765ca2948 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 30 Jul 2021 15:36:49 +0200 Subject: [PATCH] Move optional callbacks to language --- lib/ex_doc/language.ex | 25 ++++++++++++------------- lib/ex_doc/language/elixir.ex | 11 ++++++++--- lib/ex_doc/language/erlang.ex | 14 ++++++++++---- lib/ex_doc/retriever.ex | 13 +++---------- test/ex_doc/retriever/erlang_test.exs | 12 +++++++++++- 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/lib/ex_doc/language.ex b/lib/ex_doc/language.ex index ed0711ec1..9cc137cc4 100644 --- a/lib/ex_doc/language.ex +++ b/lib/ex_doc/language.ex @@ -50,22 +50,22 @@ defmodule ExDoc.Language do The map has the following keys: - * `:doc_fallback` - if set, a 0-arity function that returns DocAST which - will be used as fallback to empty docs on the function node - - * `:extra_annotations` - * `:line` - the line where the code is located * `:specs` - a list of specs that will be later formatted by `c:typespec/2` + * `:doc_fallback` - if set, a 0-arity function that returns DocAST which + will be used as fallback to empty docs on the function node + + * `:extra_annotations` - additional annotations + """ @callback function_data(entry :: tuple(), module_data()) :: %{ - doc_fallback: (() -> ExDoc.DocAST.t()) | nil, - extra_annotations: [String.t()], line: non_neg_integer() | nil, - specs: [spec_ast()] + specs: [spec_ast()], + doc_fallback: (() -> ExDoc.DocAST.t()) | nil, + extra_annotations: [String.t()] } | :skip @@ -74,22 +74,21 @@ defmodule ExDoc.Language do The map has the following keys: - * `:actual_def` - `{name, arity}` of how the callback is actually represented - in abstract code - * `:line` - the line where the code is located * `:signature` - the signature * `:specs` - a list of specs that will be later formatted by `c:typespec/2` + * `:extra_annotations` - additional annotations + """ @callback callback_data(entry :: tuple(), module_data()) :: %{ - actual_def: {atom(), arity()}, line: non_neg_integer() | nil, signature: [binary()], - specs: [spec_ast()] + specs: [spec_ast()], + extra_annotations: [String.t()] } @doc """ diff --git a/lib/ex_doc/language/elixir.ex b/lib/ex_doc/language/elixir.ex index d74d3e86a..090eae67c 100644 --- a/lib/ex_doc/language/elixir.ex +++ b/lib/ex_doc/language/elixir.ex @@ -19,6 +19,7 @@ defmodule ExDoc.Language.Elixir do title = module_title(module, type) abst_code = Erlang.get_abstract_code(module) line = Erlang.find_module_line(module, abst_code) + optional_callbacks = type == :behaviour && module.behaviour_info(:optional_callbacks) %{ module: module, @@ -34,7 +35,8 @@ defmodule ExDoc.Language.Elixir do abst_code: abst_code, specs: Erlang.get_specs(module), callbacks: Erlang.get_callbacks(module), - impls: get_impls(module) + impls: get_impls(module), + optional_callbacks: optional_callbacks } } end @@ -106,6 +108,9 @@ defmodule ExDoc.Language.Elixir do {{kind, name, arity}, anno, _signature, _doc, _metadata} = entry actual_def = actual_def(name, arity, kind) + extra_annotations = + if actual_def in module_data.private.optional_callbacks, do: ["optional"], else: [] + specs = case Map.fetch(module_data.private.callbacks, actual_def) do {:ok, specs} -> @@ -127,10 +132,10 @@ defmodule ExDoc.Language.Elixir do signature = [get_typespec_signature(hd(quoted), arity)] %{ - actual_def: actual_def, line: line, signature: signature, - specs: quoted + specs: quoted, + extra_annotations: extra_annotations } end diff --git a/lib/ex_doc/language/erlang.ex b/lib/ex_doc/language/erlang.ex index 269a6313a..76db5b1e6 100644 --- a/lib/ex_doc/language/erlang.ex +++ b/lib/ex_doc/language/erlang.ex @@ -20,6 +20,8 @@ defmodule ExDoc.Language.Erlang do ":" <> id = inspect(module) abst_code = get_abstract_code(module) line = find_module_line(module, abst_code) + type = module_type(module) + optional_callbacks = type == :behaviour && module.behaviour_info(:optional_callbacks) %{ module: module, @@ -27,14 +29,15 @@ defmodule ExDoc.Language.Erlang do language: __MODULE__, id: id, title: id, - type: module_type(module), + type: type, line: line, callback_types: [:callback], nesting_info: nil, private: %{ abst_code: abst_code, specs: get_specs(module), - callbacks: get_callbacks(module) + callbacks: get_callbacks(module), + optional_callbacks: optional_callbacks } } end @@ -72,6 +75,9 @@ defmodule ExDoc.Language.Erlang do def callback_data(entry, module_data) do {{_kind, name, arity}, anno, signature, _doc, _metadata} = entry + extra_annotations = + if {name, arity} in module_data.private.optional_callbacks, do: ["optional"], else: [] + specs = case Map.fetch(module_data.private.callbacks, {name, arity}) do {:ok, specs} -> @@ -82,10 +88,10 @@ defmodule ExDoc.Language.Erlang do end %{ - actual_def: {name, arity}, line: anno_line(anno), signature: signature, - specs: specs + specs: specs, + extra_annotations: extra_annotations } end diff --git a/lib/ex_doc/retriever.ex b/lib/ex_doc/retriever.ex index b4d1c9379..6859b0ad4 100644 --- a/lib/ex_doc/retriever.ex +++ b/lib/ex_doc/retriever.ex @@ -240,31 +240,24 @@ defmodule ExDoc.Retriever do defp get_callbacks(%{type: :behaviour} = module_data, source, groups_for_functions) do {:docs_v1, _, _, _, _, _, docs} = module_data.docs - optional_callbacks = module_data.module.behaviour_info(:optional_callbacks) for {{kind, _, _}, _, _, _, _} = doc <- docs, kind in module_data.callback_types do - get_callback(doc, source, optional_callbacks, groups_for_functions, module_data) + get_callback(doc, source, groups_for_functions, module_data) end end defp get_callbacks(_, _, _), do: [] - defp get_callback(callback, source, optional_callbacks, groups_for_functions, module_data) do + defp get_callback(callback, source, groups_for_functions, module_data) do callback_data = module_data.language.callback_data(callback, module_data) {:docs_v1, _, _, content_type, _, _, _} = module_data.docs {{kind, name, arity}, anno, _signature, doc, metadata} = callback - actual_def = callback_data.actual_def doc_line = anno_line(anno) signature = signature(callback_data.signature) specs = callback_data.specs - annotations = annotations_from_metadata(metadata) - - # actual_def is Elixir specific, but remember optional_callbacks are generic. - annotations = - if actual_def in optional_callbacks, do: ["optional" | annotations], else: annotations - + annotations = callback_data.extra_annotations ++ annotations_from_metadata(metadata) doc_ast = doc_ast(content_type, doc, file: source.path, line: doc_line + 1) group = diff --git a/test/ex_doc/retriever/erlang_test.exs b/test/ex_doc/retriever/erlang_test.exs index 34cddf361..6657be57d 100644 --- a/test/ex_doc/retriever/erlang_test.exs +++ b/test/ex_doc/retriever/erlang_test.exs @@ -108,17 +108,27 @@ defmodule ExDoc.Retriever.ErlangTest do -callback callback1() -> atom(). %% callback1/0 docs. + + -callback optional_callback1() -> atom(). + %% optional_callback1/0 docs. + + -optional_callbacks([optional_callback1/0]). """) config = %ExDoc.Config{source_url_pattern: "%{path}:%{line}"} [mod] = Retriever.docs_from_modules([:mod], config) - [callback1] = mod.docs + [callback1, optional_callback1] = mod.docs assert callback1.id == "c:callback1/0" assert callback1.type == :callback + assert callback1.annotations == [] assert DocAST.to_string(callback1.doc) == "callback1/0 docs." assert Path.basename(callback1.source_url) == "mod.erl:4" assert Erlang.autolink_spec(hd(callback1.specs), []) == "callback1() -> atom()." + + assert optional_callback1.id == "c:optional_callback1/0" + assert optional_callback1.type == :callback + assert optional_callback1.annotations == ["optional"] end @tag :otp24