Skip to content

Commit 38c672f

Browse files
authored
Skip Erlang functions that have empty docs (#1384)
1 parent d09aac0 commit 38c672f

File tree

5 files changed

+103
-61
lines changed

5 files changed

+103
-61
lines changed

lib/ex_doc/language.ex

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ defmodule ExDoc.Language do
4646
@callback module_data(module(), tuple(), ExDoc.Config.t()) :: module_data() | :skip
4747

4848
@doc """
49-
Returns a map with function information.
49+
Returns a map with function information or an atom `:skip`.
5050
5151
The map has the following keys:
5252
@@ -67,6 +67,7 @@ defmodule ExDoc.Language do
6767
line: non_neg_integer() | nil,
6868
specs: [spec_ast()]
6969
}
70+
| :skip
7071

7172
@doc """
7273
Returns a map with callback information.
@@ -83,12 +84,13 @@ defmodule ExDoc.Language do
8384
* `:specs` - a list of specs that will be later formatted by `c:typespec/2`
8485
8586
"""
86-
@callback callback_data(entry :: tuple(), module_data()) :: %{
87-
actual_def: {atom(), arity()},
88-
line: non_neg_integer() | nil,
89-
signature: [binary()],
90-
specs: [spec_ast()]
91-
}
87+
@callback callback_data(entry :: tuple(), module_data()) ::
88+
%{
89+
actual_def: {atom(), arity()},
90+
line: non_neg_integer() | nil,
91+
signature: [binary()],
92+
specs: [spec_ast()]
93+
}
9294

9395
@doc """
9496
Returns a map with type information.
@@ -103,12 +105,13 @@ defmodule ExDoc.Language do
103105
104106
* `:spec` - a spec that will be later formatted by `c:typespec/2`
105107
"""
106-
@callback type_data(entry :: tuple(), spec :: term()) :: %{
107-
type: :type | :opaque,
108-
line: non_neg_integer(),
109-
signature: [binary()],
110-
spec: spec_ast()
111-
}
108+
@callback type_data(entry :: tuple(), spec :: term()) ::
109+
%{
110+
type: :type | :opaque,
111+
line: non_neg_integer(),
112+
signature: [binary()],
113+
spec: spec_ast()
114+
}
112115

113116
@doc """
114117
Autolinks docs.

lib/ex_doc/language/elixir.ex

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,16 @@ defmodule ExDoc.Language.Elixir do
4242

4343
@impl true
4444
def function_data(entry, module_data) do
45-
{{kind, name, arity}, _anno, _signature, _doc_content, metadata} = entry
45+
{{kind, name, arity}, _anno, _signature, doc_content, metadata} = entry
4646

47+
if doc?(entry, module_data.type) do
48+
function_data(kind, name, arity, doc_content, metadata, module_data)
49+
else
50+
:skip
51+
end
52+
end
53+
54+
def function_data(kind, name, arity, _doc_content, metadata, module_data) do
4755
extra_annotations =
4856
case {kind, name, arity} do
4957
{:macro, _, _} -> ["macro"]
@@ -66,6 +74,33 @@ defmodule ExDoc.Language.Elixir do
6674
}
6775
end
6876

77+
# We are only interested in functions and macros for now
78+
defp doc?({{kind, _, _}, _, _, _, _}, _) when kind not in [:function, :macro] do
79+
false
80+
end
81+
82+
# Skip impl_for and impl_for! for protocols
83+
defp doc?({{_, name, _}, _, _, _, _}, :protocol) when name in [:impl_for, :impl_for!] do
84+
false
85+
end
86+
87+
# If content is a map, then it is ok.
88+
defp doc?({_, _, _, %{}, _}, _) do
89+
true
90+
end
91+
92+
# We keep this clause with backwards compatibility with Elixir,
93+
# from v1.12+, functions not starting with _ always default to %{}.
94+
# TODO: Remove me once we require Elixir v1.12.
95+
defp doc?({{_, name, _}, _, _, :none, _}, _type) do
96+
hd(Atom.to_charlist(name)) != ?_
97+
end
98+
99+
# Everything else is hidden.
100+
defp doc?({_, _, _, _, _}, _) do
101+
false
102+
end
103+
69104
@impl true
70105
def callback_data(entry, module_data) do
71106
{{kind, name, arity}, anno, _signature, _doc, _metadata} = entry

lib/ex_doc/language/erlang.ex

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ defmodule ExDoc.Language.Erlang do
66
alias ExDoc.{Autolink, Refs}
77

88
@impl true
9-
def module_data(_module, {:docs_v1, _, _, _, doc, _, _}, _config) when not is_map(doc) do
10-
:skip
9+
def module_data(module, docs_chunk, _config) do
10+
{:docs_v1, _, _, _, doc, _, _} = docs_chunk
11+
12+
if is_map(doc) do
13+
module_data(module, docs_chunk)
14+
else
15+
:skip
16+
end
1117
end
1218

13-
def module_data(module, docs_chunk, _config) do
19+
def module_data(module, docs_chunk) do
1420
":" <> id = inspect(module)
1521
abst_code = get_abstract_code(module)
1622
line = find_module_line(module, abst_code)
@@ -35,8 +41,16 @@ defmodule ExDoc.Language.Erlang do
3541

3642
@impl true
3743
def function_data(entry, module_data) do
38-
{{_kind, name, arity}, _anno, _signature, _doc_content, _metadata} = entry
44+
{{kind, name, arity}, _anno, _signature, doc_content, _metadata} = entry
45+
46+
if kind == :function and is_map(doc_content) do
47+
function_data(name, arity, doc_content, module_data)
48+
else
49+
:skip
50+
end
51+
end
3952

53+
defp function_data(name, arity, _doc_content, module_data) do
4054
specs =
4155
case Map.fetch(module_data.private.specs, {name, arity}) do
4256
{:ok, specs} ->

lib/ex_doc/retriever.ex

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -166,49 +166,24 @@ defmodule ExDoc.Retriever do
166166

167167
## Function helpers
168168

169-
defp get_docs(%{type: type, docs: docs} = module_data, source, groups_for_functions) do
170-
{:docs_v1, _, _, _, _, _, doc_elements} = docs
169+
defp get_docs(module_data, source, groups_for_functions) do
170+
{:docs_v1, _, _, _, _, _, doc_elements} = module_data.docs
171171

172-
function_doc_elements =
173-
for doc_element <- doc_elements, doc?(doc_element, type) do
174-
get_function(doc_element, source, module_data, groups_for_functions)
175-
end
176-
177-
filter_defaults(function_doc_elements)
178-
end
179-
180-
# TODO: Elixir specific
181-
# We are only interested in functions and macros for now
182-
defp doc?({{kind, _, _}, _, _, _, _}, _) when kind not in [:function, :macro] do
183-
false
184-
end
185-
186-
# TODO: Elixir specific
187-
# Skip impl_for and impl_for! for protocols
188-
defp doc?({{_, name, _}, _, _, _, _}, :protocol) when name in [:impl_for, :impl_for!] do
189-
false
190-
end
172+
nodes =
173+
Enum.flat_map(doc_elements, fn doc_element ->
174+
case module_data.language.function_data(doc_element, module_data) do
175+
:skip ->
176+
[]
191177

192-
# If content is a map, then it is ok.
193-
defp doc?({_, _, _, %{}, _}, _) do
194-
true
195-
end
196-
197-
# We keep this clause with backwards compatibility with Elixir,
198-
# from v1.12+, functions not starting with _ always default to %{}.
199-
# TODO: Remove me once we require Elixir v1.12.
200-
defp doc?({{_, name, _}, _, _, :none, _}, _type) do
201-
hd(Atom.to_charlist(name)) != ?_
202-
end
178+
function_data ->
179+
[get_function(doc_element, function_data, source, module_data, groups_for_functions)]
180+
end
181+
end)
203182

204-
# Everything else is hidden.
205-
defp doc?({_, _, _, _, _}, _) do
206-
false
183+
filter_defaults(nodes)
207184
end
208185

209-
defp get_function(doc_element, source, module_data, groups_for_functions) do
210-
function_data = module_data.language.function_data(doc_element, module_data)
211-
186+
defp get_function(doc_element, function_data, source, module_data, groups_for_functions) do
212187
{:docs_v1, _, _, content_type, _, _, _} = module_data.docs
213188
{{type, name, arity}, anno, signature, doc_content, metadata} = doc_element
214189
doc_line = anno_line(anno)
@@ -249,14 +224,14 @@ defmodule ExDoc.Retriever do
249224
for default <- (arity - defaults)..(arity - 1), do: {name, default}
250225
end
251226

252-
defp filter_defaults(docs) do
253-
Enum.map(docs, &filter_defaults(&1, docs))
227+
defp filter_defaults(nodes) do
228+
Enum.map(nodes, &filter_defaults(&1, nodes))
254229
end
255230

256-
defp filter_defaults(doc, docs) do
257-
update_in(doc.defaults, fn defaults ->
231+
defp filter_defaults(node, nodes) do
232+
update_in(node.defaults, fn defaults ->
258233
Enum.reject(defaults, fn {name, arity} ->
259-
Enum.any?(docs, &match?(%{name: ^name, arity: ^arity}, &1))
234+
Enum.any?(nodes, &match?(%{name: ^name, arity: ^arity}, &1))
260235
end)
261236
end)
262237
end

test/ex_doc/retriever/erlang_test.exs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ defmodule ExDoc.Retriever.ErlangTest do
8585
[] = Retriever.docs_from_modules([:mod], %ExDoc.Config{})
8686
end
8787

88+
@tag :otp23
89+
@tag :otp24
90+
test "function with no docs is skipped", c do
91+
erlc(c, :mod, ~S"""
92+
%% @doc Docs.
93+
-module(mod).
94+
-export([f/0]).
95+
96+
f() -> ok.
97+
""")
98+
99+
[mod] = Retriever.docs_from_modules([:mod], %ExDoc.Config{})
100+
assert mod.docs == []
101+
end
102+
88103
@tag :otp24
89104
test "callbacks", c do
90105
erlc(c, :mod, ~S"""

0 commit comments

Comments
 (0)