Skip to content
Closed
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
40 changes: 28 additions & 12 deletions lib/ex_doc/retriever.ex
Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,9 @@ defmodule ExDoc.Retriever do
function = actual_def(name, arity, type)
line = find_actual_line(module_info.abst_code, function, :function) || doc_line

doc = docstring(doc, name, arity, Map.fetch(module_info.impls, {name, arity}))

specs = module_info.specs
|> Map.get(function, [])
|> Enum.map(&Typespec.spec_to_ast(name, &1))

specs =
if type == :defmacro do
Enum.map(specs, &remove_first_macro_arg/1)
else
specs
end
impl = Map.fetch(module_info.impls, {name, arity})
doc = docstring(doc, name, arity, impl)
specs = get_specs(module_info, type, function, name, arity, impl)

annotations =
case {type, name, arity} do
Expand Down Expand Up @@ -297,6 +288,31 @@ defmodule ExDoc.Retriever do
end
end

defp get_specs(module_info, type, function, name, arity, impl) do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function takes too many parameters (arity is 6, max is 5).

specs =
module_info.specs
|> Map.get(function, [])
|> Enum.map(&Typespec.spec_to_ast(name, &1))

specs =
if type == :defmacro do
Enum.map(specs, &remove_first_macro_arg/1)
else
specs
end

copy_callback_specs(specs, name, arity, impl)
end

defp copy_callback_specs([], name, arity, {:ok, behaviour}) do
callbacks = Kernel.Typespec.beam_callbacks(behaviour)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is private API :( any other options?

{_, specs} = List.keyfind(callbacks, {name, arity}, 0)
Enum.map(specs, &Typespec.spec_to_ast(name, &1))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in similar code in IEx [1] we're doing Macro.prewalk(&drop_macro_env/1), wasn't sure if we need it so I left it out.

[1] https://github.com/elixir-lang/elixir/blob/v1.5.3/lib/iex/lib/iex/introspection.ex#L434

end
defp copy_callback_specs(specs, _, _, _) do
specs
end

defp docstring(nil, name, arity, {:ok, behaviour}) do
info = "Callback implementation for `c:#{inspect behaviour}.#{name}/#{arity}`."
callback_docs = Code.get_docs(behaviour, :callback_docs) || []
Expand Down
16 changes: 12 additions & 4 deletions test/ex_doc/retriever_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,7 @@ defmodule ExDoc.RetrieverTest do
end

test "undocumented callback implementations get default doc" do
[module_node] =
["CustomBehaviourOne", "CustomBehaviourTwo", "CustomBehaviourImpl"]
|> docs_from_files()
|> Enum.filter(&match?(%ExDoc.ModuleNode{id: "CustomBehaviourImpl"}, &1))
[module_node] = docs_from_files ["CustomBehaviourImpl"]
docs = module_node.docs
assert Enum.map(docs, &(&1.id)) == ["bye/1", "greet/1", "hello/1"]
assert Enum.at(docs, 0).doc ==
Expand All @@ -245,6 +242,17 @@ defmodule ExDoc.RetrieverTest do
"Callback implementation for `c:CustomBehaviourOne.hello/1`."
end

test "callback implementations get specs from behaviour" do
[module_node] = docs_from_files ["CustomBehaviourImpl"]

[_, greet, hello] = module_node.docs
assert greet.name == :greet
assert Macro.to_string(greet.specs) == "[greet(integer() | String.t()) :: integer()]"

assert hello.name == :hello
assert Macro.to_string(hello.specs) == "[hello(non_neg_integer()) :: non_neg_integer()]"
end

## PROTOCOLS

test "docs_from_files properly tag protocols" do
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/behaviour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ defmodule CustomBehaviourImpl do
@behaviour CustomBehaviourOne
@behaviour CustomBehaviourTwo

@spec hello(non_neg_integer()) :: non_neg_integer()
def hello(i), do: i

def greet(i), do: i

@doc "A doc for this so it doesn't use 'Callback implementation for'"
Expand Down