Skip to content

Commit

Permalink
Simplify retrieving line and signature
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekmach committed Jul 13, 2021
1 parent ede4ec6 commit 0fb23da
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 47 deletions.
30 changes: 15 additions & 15 deletions lib/ex_doc/language.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ defmodule ExDoc.Language do
* `:extra_annotations`
* `:line` - if set, overrides the line where the code is located
* `:line` - the line where the code is located
* `:specs` - a list of specs that will be later formatted by `c:typespec/2`
Expand All @@ -66,18 +66,17 @@ defmodule ExDoc.Language do
* `:actual_def` - `{name, arity}` of how the callback is actually represented
in abstract code
* `:line` - if set, returns the line where the code is located
* `:line` - the line where the code is located
* `:signature_fallback` - if set, a 0-arity function that returns signature which
which will be used as a fallback to empty signature on the callback node
* `:signature` - the signature
* `:specs` - a list of specs that will be later formatted by `c:typespec/2`
"""
@callback callback_data(entry :: tuple(), module_state()) :: %{
actual_def: {atom(), arity()},
line: non_neg_integer() | nil,
signature_fallback: (() -> String.t()) | nil,
signature: [binary()],
specs: [spec_ast()]
}

Expand All @@ -86,19 +85,20 @@ defmodule ExDoc.Language do
The map has the following keys:
* `:spec` - a spec that will be later formatted by `c:typespec/2`
* `:type` - `:type` or `:opaque`
* `:line` - the line where the code is located
* `:signature_fallback` - if set, a 0-arity function that returns signature which
which will be used as a fallback to empty signature on the callback node
* `:signature` - the signature
* `:spec` - a spec that will be later formatted by `c:typespec/2`
"""
@callback type_data(entry :: tuple(), spec :: term()) :: data
when data: %{
type: :type | :opaque,
line: non_neg_integer(),
spec: spec_ast(),
signature_fallback: (() -> String.t()) | nil
}
@callback type_data(entry :: tuple(), spec :: term()) :: %{
type: :type | :opaque,
line: non_neg_integer(),
signature: [binary()],
spec: spec_ast()
}

@doc """
Autolinks docs.
Expand Down
44 changes: 28 additions & 16 deletions lib/ex_doc/language/elixir.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule ExDoc.Language.Elixir do

@impl true
def callback_data(entry, module_state) do
{{kind, name, arity}, _anno, _signature, _doc, _metadata} = entry
{{kind, name, arity}, anno, signature, _doc, _metadata} = entry
actual_def = actual_def(name, arity, kind)

specs =
Expand All @@ -65,41 +65,53 @@ defmodule ExDoc.Language.Elixir do
if specs != [] do
{:type, anno, _, _} = hd(specs)
anno_line(anno)
else
anno_line(anno)
end

specs = Enum.map(specs, &Code.Typespec.spec_to_quoted(name, &1))
quoted = Enum.map(specs, &Code.Typespec.spec_to_quoted(name, &1))

signature =
if signature != [] do
# TODO: this is in case Elixir ever emits non-empty signature for callbacks. Will it?
[]
else
[get_typespec_signature(hd(quoted), arity)]
end

%{
actual_def: actual_def,
line: line,
signature_fallback: fn ->
if specs != [] do
get_typespec_signature(hd(specs), arity)
end
end,
specs: specs
signature: signature,
specs: quoted
}
end

@impl true
def type_data(entry, module_state) do
{{kind, name, arity}, _anno, _signature, _doc, _metadata} = entry
{{kind, name, arity}, _anno, signature, _doc, _metadata} = entry

%{type: type, spec: spec, line: line} = type_from_module_state(name, arity, module_state)
spec = spec |> Code.Typespec.type_to_quoted() |> process_type_ast(kind)
%{type: type, spec: spec, line: line} = type_from_module_state(module_state, name, arity)
quoted = spec |> Code.Typespec.type_to_quoted() |> process_type_ast(kind)

signature =
if signature != [] do
# TODO: this is in case Elixir ever emits non-empty signature for types. Will it?
signature
else
[get_typespec_signature(quoted, arity)]
end

%{
type: type,
line: line,
spec: spec,
signature_fallback: fn ->
get_typespec_signature(spec, arity)
end
spec: quoted,
signature: signature
}
end

@doc false
def type_from_module_state(name, arity, module_state) do
def type_from_module_state(module_state, name, arity) do
Enum.find_value(module_state.abst_code, fn
{:attribute, anno, type, {^name, _, args} = spec} ->
if type in [:opaque, :type] and length(args) == arity do
Expand Down
44 changes: 35 additions & 9 deletions lib/ex_doc/language/erlang.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule ExDoc.Language.Erlang do

@impl true
def callback_data(entry, module_state) do
{{_kind, name, arity}, _anno, _signature, _doc, _metadata} = entry
{{_kind, name, arity}, anno, signature, _doc, _metadata} = entry

specs =
case Map.fetch(module_state.callbacks, {name, arity}) do
Expand All @@ -54,24 +54,24 @@ defmodule ExDoc.Language.Erlang do

%{
actual_def: {name, arity},
line: nil,
signature_fallback: fn -> nil end,
line: anno_line(anno),
signature: signature,
specs: specs
}
end

@impl true
def type_data(entry, module_state) do
{{_kind, name, arity}, _anno, _signature, _doc, _metadata} = entry
{{_kind, name, arity}, _anno, signature, _doc, _metadata} = entry

%{type: type, spec: spec, line: line} =
ExDoc.Language.Elixir.type_from_module_state(name, arity, module_state)
ExDoc.Language.Elixir.type_from_module_state(module_state, name, arity)

%{
type: type,
line: line,
spec: {:attribute, 0, :type, spec},
signature_fallback: fn -> nil end
signature: signature
}
end

Expand Down Expand Up @@ -131,6 +131,10 @@ defmodule ExDoc.Language.Erlang do

[app, module] ->
autolink_app({:module, app, module}, config)

_ ->
# TODO investigate building OTP docs
ast
end

"https://erlang.org/doc/link/seemfa" ->
Expand All @@ -140,6 +144,10 @@ defmodule ExDoc.Language.Erlang do

[app, mfa] ->
autolink_app({:function, app, mfa}, config)

_ ->
# TODO investigate building OTP docs
ast
end

"https://erlang.org/doc/link/seetype" ->
Expand All @@ -152,8 +160,14 @@ defmodule ExDoc.Language.Erlang do
end

"https://erlang.org/doc/link/seeapp" ->
[app, "index"] = String.split(attrs[:href], ":")
autolink_app({:app, app}, config)
case String.split(attrs[:href], ":") do
[app, "index"] ->
autolink_app({:app, app}, config)

_ ->
# TODO investigate building OTP docs
ast
end

_ ->
ast
Expand Down Expand Up @@ -356,7 +370,16 @@ defmodule ExDoc.Language.Erlang do
defp replace(formatted, acc, config) do
String.replace(formatted, Enum.map(acc, &"#{elem(&1, 0)}("), fn string ->
string = String.trim_trailing(string, "(")
{^string, ref} = pop()
{other, ref} = pop()

if string != other do
Autolink.maybe_warn(
"internal inconsistency, please submit bug: #{inspect(string)} != #{inspect(other)}",
config,
nil,
nil
)
end

url =
case ref do
Expand Down Expand Up @@ -438,4 +461,7 @@ defmodule ExDoc.Language.Erlang do
end)
|> Enum.join("\n")
end

defp anno_line(line) when is_integer(line), do: abs(line)
defp anno_line(anno), do: anno |> :erl_anno.line() |> abs()
end
13 changes: 6 additions & 7 deletions lib/ex_doc/retriever.ex
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,12 @@ defmodule ExDoc.Retriever do
callback_data = module_state.language.callback_data(callback, module_state)

{:docs_v1, _, _, content_type, _, _, _} = module_state.docs
{{kind, name, arity}, anno, signature, doc, metadata} = callback
{{kind, name, arity}, anno, _signature, doc, metadata} = callback
actual_def = callback_data.actual_def
doc_line = anno_line(anno)
signature = signature(signature)

signature = signature(callback_data.signature)
specs = callback_data.specs
signature = signature || callback_data.signature_fallback.() || "#{name}/#{arity}"
annotations = annotations_from_metadata(metadata)

# actual_def is Elixir specific, but remember optional_callbacks are generic.
Expand All @@ -354,7 +354,7 @@ defmodule ExDoc.Retriever do
signature: signature,
specs: specs,
source_path: source.path,
source_url: source_link(source, callback_data.line || doc_line),
source_url: source_link(source, callback_data.line),
type: kind,
annotations: annotations
}
Expand Down Expand Up @@ -402,13 +402,12 @@ defmodule ExDoc.Retriever do

defp get_type(type_entry, source, module_state) do
{:docs_v1, _, _, content_type, _, _, _} = module_state.docs
{{_, name, arity}, anno, signature, doc, metadata} = type_entry
{{_, name, arity}, anno, _signature, doc, metadata} = type_entry
doc_line = anno_line(anno)
annotations = annotations_from_metadata(metadata)

type_data = module_state.language.type_data(type_entry, module_state)
signature = signature(signature) || type_data.signature_fallback.()

signature = signature(type_data.signature)
annotations = if type_data.type == :opaque, do: ["opaque" | annotations], else: annotations
doc_ast = doc_ast(content_type, doc, file: source.path)

Expand Down

0 comments on commit 0fb23da

Please sign in to comment.