Packages
ex_doc
0.25.3
0.40.3
0.40.2
0.40.1
0.40.0
0.39.3
0.39.2
0.39.1
0.39.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.3
0.37.2
0.37.1
0.37.0
0.37.0-rc.2
0.37.0-rc.1
0.37.0-rc.0
0.36.1
0.36.0
0.35.1
0.35.0
0.34.2
0.34.1
0.34.0
0.33.0
0.32.2
0.32.1
0.32.0
0.31.2
0.31.1
0.31.0
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.6
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.3
0.27.2
0.27.1
0.27.0
0.26.0
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.2
0.24.1
0.24.0
0.23.0
0.22.6
0.22.5
0.22.4
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.19.0-rc
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
retired
0.17.1
0.17.0
retired
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
ExDoc is a documentation generation tool for Elixir
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