Current section
Files
Jump to
Current section
Files
lib/ex_doc/refs.ex
defmodule ExDoc.Refs do
@moduledoc false
# A read-through cache of documentation references.
#
# A given ref is always associated with a module. If we don't have a ref
# in the cache we fetch the module's docs chunk and fill in the cache.
#
# If the module does not have the docs chunk, we fetch it's functions,
# callbacks and types from other sources.
@typep entry() :: {ref(), visibility()}
@typep ref() ::
{:module, module()}
| {kind(), module(), name :: atom(), arity()}
@typep kind() :: :function | :callback | :type
@typep visibility() :: :hidden | :public | :undefined
@name __MODULE__
use GenServer
@spec start_link(any()) :: GenServer.on_start()
def start_link(arg) do
GenServer.start_link(__MODULE__, arg, name: @name)
end
@spec init(any()) :: {:ok, nil}
def init(_) do
:ets.new(@name, [:named_table, :public, :set])
{:ok, nil}
end
@spec clear() :: :ok
def clear() do
:ets.delete_all_objects(@name)
:ok
end
@spec get_visibility(ref()) :: visibility()
def get_visibility(ref) do
case lookup(ref) do
{:ok, visibility} ->
visibility
:error ->
fetch(ref)
end
end
defp lookup(ref) do
case :ets.lookup(@name, ref) do
[{^ref, visibility}] ->
{:ok, visibility}
[] ->
:error
end
rescue
_ ->
:error
end
@spec insert([entry()]) :: :ok
def insert(entries) do
true = :ets.insert(@name, entries)
:ok
end
@spec insert_from_chunk(module, tuple()) :: :ok
def insert_from_chunk(module, result) do
entries = fetch_entries(module, result)
insert(entries)
:ok
end
defp fetch({:module, module} = ref) do
entries = fetch_entries(module, ExDoc.Utils.Code.fetch_docs(module))
insert(entries)
Map.get(Map.new(entries), ref, :undefined)
end
defp fetch({_kind, module, _name, _arity} = ref) do
with module_visibility <- fetch({:module, module}),
true <- module_visibility in [:public, :hidden],
{:ok, visibility} <- lookup(ref) do
visibility
else
_ ->
:undefined
end
end
defp fetch_entries(module, result) do
case result do
{:docs_v1, _, _, _, module_doc, _, docs} ->
module_visibility = visibility(module_doc)
for {{kind, name, arity}, _, _, doc, metadata} <- docs do
ref_kind = to_ref_kind(kind)
visibility = visibility(module_doc, {ref_kind, name, doc})
for arity <- (arity - (metadata[:defaults] || 0))..arity do
{{ref_kind, module, name, arity}, visibility}
end
end
|> List.flatten()
|> Enum.concat([
{{:module, module}, module_visibility}
| to_refs(types(module, [:typep]), module, :type, :hidden)
])
{:error, _} ->
if Code.ensure_loaded?(module) do
(to_refs(exports(module), module, :function) ++
to_refs(callbacks(module), module, :callback) ++
to_refs(types(module, [:type, :opaque]), module, :type) ++
to_refs(types(module, [:typep]), module, :type, :hidden))
|> Enum.concat([{{:module, module}, :public}])
else
[{{:module, module}, :undefined}]
end
end
end
defguardp has_no_docs(doc) when doc == :none or doc == %{}
defp starts_with_underscore?(name), do: hd(Atom.to_charlist(name)) == ?_
defp visibility(:hidden),
do: :hidden
defp visibility(_module_doc),
do: :public
defp visibility(_module_doc, {kind, _name, _doc})
when kind not in [:callback, :function, :type],
do: raise(ArgumentError, "Unknown kind #{inspect(kind)}")
defp visibility(:hidden, {_kind, _name, _doc}),
do: :hidden
defp visibility(_, {_kind, _name, :hidden}),
do: :hidden
defp visibility(_, {kind, name, doc}) when has_no_docs(doc) do
cond do
kind in [:callback, :type] ->
:public
kind == :function and starts_with_underscore?(name) ->
:hidden
kind == :function ->
:public
end
end
defp visibility(_, {_, _, _}) do
:public
end
defp to_ref_kind(:macro), do: :function
defp to_ref_kind(:macrocallback), do: :callback
defp to_ref_kind(other), do: other
defp exports(module) do
if function_exported?(module, :__info__, 1) do
module.__info__(:functions) ++ module.__info__(:macros)
else
module.module_info(:exports)
end
end
defp callbacks(module) do
if function_exported?(module, :behaviour_info, 1) do
module.behaviour_info(:callbacks)
else
[]
end
end
defp types(module, kind_list) do
case Code.Typespec.fetch_types(module) do
{:ok, list} ->
for {kind, {name, _, args}} <- list,
kind in kind_list do
{name, length(args)}
end
:error ->
[]
end
end
defp to_refs(list, module, kind, visibility \\ :public) do
for {name, arity} <- list do
{{kind, module, name, arity}, visibility}
end
end
end