refs.ex (4778B)
1 defmodule ExDoc.Refs do 2 @moduledoc false 3 4 # A read-through cache of documentation references. 5 # 6 # A given ref is always associated with a module. If we don't have a ref 7 # in the cache we fetch the module's docs chunk and fill in the cache. 8 # 9 # If the module does not have the docs chunk, we fetch it's functions, 10 # callbacks and types from other sources. 11 12 @typep entry() :: {ref(), visibility()} 13 14 @typep ref() :: 15 {:module, module()} 16 | {kind(), module(), name :: atom(), arity()} 17 @typep kind() :: :function | :callback | :type 18 @typep visibility() :: :hidden | :public | :undefined | :partial 19 20 @name __MODULE__ 21 22 use GenServer 23 24 @spec start_link(any()) :: GenServer.on_start() 25 def start_link(arg) do 26 GenServer.start_link(__MODULE__, arg, name: @name) 27 end 28 29 @spec init(any()) :: {:ok, nil} 30 def init(_) do 31 :ets.new(@name, [:named_table, :public, :set]) 32 {:ok, nil} 33 end 34 35 @spec clear() :: :ok 36 def clear() do 37 :ets.delete_all_objects(@name) 38 :ok 39 end 40 41 @spec get_visibility(ref()) :: visibility() 42 def get_visibility(ref) do 43 case lookup(ref) do 44 {:ok, visibility} -> 45 visibility 46 47 :error -> 48 case fetch(ref) do 49 {:ok, visibility} -> visibility 50 :error -> :undefined 51 end 52 end 53 end 54 55 @spec insert([entry()]) :: :ok 56 def insert(entries) do 57 true = :ets.insert(@name, entries) 58 :ok 59 end 60 61 @spec insert_from_chunk(module, tuple()) :: :ok 62 def insert_from_chunk(module, result) do 63 module 64 |> fetch_entries(result) 65 |> insert() 66 67 :ok 68 end 69 70 defp lookup(ref) do 71 case :ets.lookup(@name, ref) do 72 [{^ref, visibility}] -> {:ok, visibility} 73 [] -> :error 74 end 75 rescue 76 _ -> :error 77 end 78 79 defp fetch({:module, module} = ref) do 80 insert_from_chunk(module, Code.fetch_docs(module)) 81 lookup(ref) 82 end 83 84 defp fetch({_kind, module, _name, _arity} = ref) do 85 get_visibility({:module, module}) 86 lookup(ref) 87 end 88 89 defp fetch_entries(module, result) do 90 case result do 91 {:docs_v1, _, _, _, module_doc, _, docs} -> 92 module_visibility = visibility(module_doc) 93 94 [{{:module, module}, module_visibility}] ++ 95 for {{kind, name, arity}, _, _, doc, metadata} <- docs, 96 ref_kind = to_ref_kind(kind), 97 visibility = visibility(module_doc, {ref_kind, name, doc}), 98 arity <- (arity - (metadata[:defaults] || 0))..arity do 99 {{ref_kind, module, name, arity}, visibility} 100 end 101 102 {:error, _} -> 103 if Code.ensure_loaded?(module) do 104 # We say it is limited because the types may not actually be available in the beam file. 105 [{{:module, module}, :limited}] ++ 106 to_refs(exports(module), module, :function) ++ 107 to_refs(callbacks(module), module, :callback) ++ 108 to_refs(types(module, [:type, :opaque]), module, :type) 109 else 110 [{{:module, module}, :undefined}] 111 end 112 end 113 end 114 115 defguardp has_no_docs(doc) when doc == :none or doc == %{} 116 117 defp starts_with_underscore?(name), do: hd(Atom.to_charlist(name)) == ?_ 118 119 defp visibility(:hidden), 120 do: :hidden 121 122 defp visibility(_module_doc), 123 do: :public 124 125 defp visibility(_module_doc, {kind, _name, _doc}) 126 when kind not in [:callback, :function, :type], 127 do: raise(ArgumentError, "Unknown kind #{inspect(kind)}") 128 129 defp visibility(:hidden, {_kind, _name, _doc}), 130 do: :hidden 131 132 defp visibility(_, {_kind, _name, :hidden}), 133 do: :hidden 134 135 defp visibility(_, {kind, name, doc}) when has_no_docs(doc) do 136 cond do 137 kind in [:callback, :type] -> 138 :public 139 140 kind == :function and starts_with_underscore?(name) -> 141 :hidden 142 143 kind == :function -> 144 :public 145 end 146 end 147 148 defp visibility(_, {_, _, _}) do 149 :public 150 end 151 152 defp to_ref_kind(:macro), do: :function 153 defp to_ref_kind(:macrocallback), do: :callback 154 defp to_ref_kind(other), do: other 155 156 defp exports(module) do 157 if function_exported?(module, :__info__, 1) do 158 module.__info__(:functions) ++ module.__info__(:macros) 159 else 160 module.module_info(:exports) 161 end 162 end 163 164 defp callbacks(module) do 165 if function_exported?(module, :behaviour_info, 1) do 166 module.behaviour_info(:callbacks) 167 else 168 [] 169 end 170 end 171 172 defp types(module, kind_list) do 173 case Code.Typespec.fetch_types(module) do 174 {:ok, list} -> 175 for {kind, {name, _, args}} <- list, 176 kind in kind_list do 177 {name, length(args)} 178 end 179 180 :error -> 181 [] 182 end 183 end 184 185 defp to_refs(list, module, kind, visibility \\ :public) do 186 for {name, arity} <- list do 187 {{kind, module, name, arity}, visibility} 188 end 189 end 190 end