Packages
ex_doc
0.30.6
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 | :partial
@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 ->
case fetch(ref) do
{:ok, visibility} -> visibility
:error -> :undefined
end
end
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
module
|> fetch_entries(result)
|> insert()
:ok
end
defp lookup(ref) do
case :ets.lookup(@name, ref) do
[{^ref, visibility}] -> {:ok, visibility}
[] -> :error
end
rescue
_ -> :error
end
defp fetch({:module, module} = ref) do
insert_from_chunk(module, Code.fetch_docs(module))
lookup(ref)
end
defp fetch({_kind, module, _name, _arity} = ref) do
get_visibility({:module, module})
lookup(ref)
end
defp fetch_entries(module, result) do
case result do
{:docs_v1, _, _, _, module_doc, _, docs} ->
module_visibility = visibility(module_doc)
[{{:module, module}, module_visibility}] ++
for {{kind, name, arity}, _, _, doc, metadata} <- docs,
ref_kind = to_ref_kind(kind),
visibility = visibility(module_doc, {ref_kind, name, doc}),
arity <- (arity - (metadata[:defaults] || 0))..arity do
{{ref_kind, module, name, arity}, visibility}
end
{:error, _reason} ->
with true <- :code.which(module) != :non_existing,
true <- Code.ensure_loaded?(module) do
# We say it is limited because the types may not actually be available in the beam file.
[{{:module, module}, :limited}] ++
to_refs(exports(module), module, :function) ++
to_refs(callbacks(module), module, :callback) ++
to_refs(types(module, [:type, :opaque]), module, :type)
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