Packages
livebook
0.8.2
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/intellisense/docs.ex
defmodule Livebook.Intellisense.Docs do
@moduledoc false
# This module is responsible for extracting and normalizing
# information like documentation, signatures and specs.
@type member_info :: %{
kind: member_kind(),
name: atom(),
arity: non_neg_integer(),
from_default: boolean(),
documentation: documentation(),
signatures: list(signature()),
specs: list(spec()),
meta: meta()
}
@type member_kind :: :function | :macro | :type
@type documentation :: {format :: String.t(), content :: String.t()} | :hidden | nil
@type signature :: String.t()
@type meta :: map()
@typedoc """
A single spec annotation in the Erlang Abstract Format.
"""
@type spec :: term()
@doc """
Fetches documentation for the given module if available.
"""
@spec get_module_documentation(module()) :: documentation()
def get_module_documentation(module) do
case Code.fetch_docs(module) do
{:docs_v1, _, _, format, %{"en" => docstring}, _, _} ->
{format, docstring}
{:docs_v1, _, _, _, :hidden, _, _} ->
:hidden
_ ->
nil
end
end
@doc """
Fetches information about the given module members if available.
The given `members` are used to limit the result to the relevant
entries. Arity may be given as `:any`, in which case all entries
matching the name are returned.
Functions with default arguments are normalized, such that each
arity is treated as a separate member, sourcing documentation
from the original one.
## Options
* `:kinds` - a list of member kinds to limit the lookup to.
Valid kinds are `:function`, `:macro` and `:type`. Defaults
to all kinds
"""
@spec lookup_module_members(
module(),
list({name :: atom(), arity :: non_neg_integer() | :any}),
keyword()
) :: list(member_info())
def lookup_module_members(module, members, opts \\ []) do
members = MapSet.new(members)
kinds = opts[:kinds] || [:function, :macro, :type]
specs =
with true <- :function in kinds or :macro in kinds,
{:ok, specs} <- Code.Typespec.fetch_specs(module) do
Map.new(specs)
else
_ -> %{}
end
case Code.fetch_docs(module) do
{:docs_v1, _, _, format, _, _, docs} ->
for {{kind, name, base_arity}, _line, signatures, doc, meta} <- docs,
kind in kinds,
defaults = Map.get(meta, :defaults, 0),
arity <- (base_arity - defaults)..base_arity,
MapSet.member?(members, {name, arity}) or MapSet.member?(members, {name, :any}),
do: %{
kind: kind,
name: name,
arity: arity,
from_default: arity != base_arity,
documentation: documentation(doc, format),
signatures: signatures,
specs: Map.get(specs, {name, base_arity}, []),
meta: meta
}
_ ->
[]
end
end
defp documentation(%{"en" => docstr}, format), do: {format, docstr}
defp documentation(:hidden, _format), do: :hidden
defp documentation(_doc, _format), do: nil
@doc """
Determines a more specific module type if any.
"""
@spec get_module_subtype(module) ::
:protocol | :implementation | :exception | :struct | :behaviour | nil
def get_module_subtype(module) do
cond do
not ensure_loaded?(module) ->
nil
function_exported?(module, :__protocol__, 1) ->
:protocol
function_exported?(module, :__impl__, 1) ->
:implementation
function_exported?(module, :__struct__, 0) ->
if function_exported?(module, :exception, 1) do
:exception
else
:struct
end
function_exported?(module, :behaviour_info, 1) ->
:behaviour
true ->
nil
end
end
# In case insensitive file systems, attempting to load
# Elixir will log a warning in the terminal as it wrongly
# loads elixir.beam, so we explicitly list it.
defp ensure_loaded?(Elixir), do: false
defp ensure_loaded?(module), do: Code.ensure_loaded?(module)
end