411 lines
12 KiB
Elixir
411 lines
12 KiB
Elixir
Code.require_file("../test_helper.exs", __DIR__)
|
|
|
|
defmodule Kernel.DocsTest do
|
|
use ExUnit.Case
|
|
|
|
import PathHelpers
|
|
|
|
defmacro wrong_doc_baz do
|
|
quote do
|
|
@doc "Wrong doc"
|
|
@doc since: "1.2"
|
|
def baz(_arg)
|
|
def baz(arg), do: arg + 1
|
|
end
|
|
end
|
|
|
|
test "attributes format" do
|
|
defmodule DocAttributes do
|
|
@moduledoc "Module doc"
|
|
assert @moduledoc == "Module doc"
|
|
assert Module.get_attribute(__MODULE__, :moduledoc) == {__ENV__.line - 2, "Module doc"}
|
|
|
|
@typedoc "Type doc"
|
|
assert @typedoc == "Type doc"
|
|
assert Module.get_attribute(__MODULE__, :typedoc) == {__ENV__.line - 2, "Type doc"}
|
|
@type foobar :: any
|
|
|
|
@doc "Function doc"
|
|
assert @doc == "Function doc"
|
|
assert Module.get_attribute(__MODULE__, :doc) == {__ENV__.line - 2, "Function doc"}
|
|
|
|
def foobar() do
|
|
:ok
|
|
end
|
|
end
|
|
end
|
|
|
|
test "compiled without docs" do
|
|
Code.compiler_options(docs: false)
|
|
|
|
write_beam(
|
|
defmodule WithoutDocs do
|
|
@moduledoc "Module doc"
|
|
|
|
@doc "Some doc"
|
|
def foobar(arg), do: arg
|
|
end
|
|
)
|
|
|
|
assert Code.fetch_docs(WithoutDocs) == {:error, :chunk_not_found}
|
|
after
|
|
Code.compiler_options(docs: true)
|
|
end
|
|
|
|
test "compiled in memory does not have accessible docs" do
|
|
defmodule InMemoryDocs do
|
|
@moduledoc "Module doc"
|
|
|
|
@doc "Some doc"
|
|
def foobar(arg), do: arg
|
|
end
|
|
|
|
assert Code.fetch_docs(InMemoryDocs) == {:error, :module_not_found}
|
|
end
|
|
|
|
test "non-existent beam file" do
|
|
assert {:error, :module_not_found} = Code.fetch_docs("bad.beam")
|
|
end
|
|
|
|
test "raises on invalid @doc since: ..." do
|
|
assert_raise ArgumentError, ~r"should be a string representing the version", fn ->
|
|
defmodule InvalidSince do
|
|
@doc since: 1.2
|
|
def foo, do: :bar
|
|
end
|
|
end
|
|
end
|
|
|
|
test "raises on invalid @doc" do
|
|
assert_raise ArgumentError, ~r/When set dynamically, it should be {line, doc}/, fn ->
|
|
defmodule DocAttributesFormat do
|
|
Module.put_attribute(__MODULE__, :moduledoc, "Other")
|
|
end
|
|
end
|
|
|
|
message = ~r/should be either false, nil, a string, or a keyword list/
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
defmodule AtSyntaxDocAttributesFormat do
|
|
@moduledoc :not_a_binary
|
|
end
|
|
end
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
defmodule AtSyntaxDocAttributesFormat do
|
|
@moduledoc true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "compiled with docs" do
|
|
test "infers signatures" do
|
|
write_beam(
|
|
defmodule SignatureDocs do
|
|
def arg_names([], [], %{}, [], %{}), do: false
|
|
|
|
@year 2015
|
|
def with_defaults(@year, arg \\ 0, year \\ @year, fun \\ &>=/2) do
|
|
{fun, arg + year}
|
|
end
|
|
|
|
def with_map_and_default(%{key: value} \\ %{key: :default}), do: value
|
|
def with_struct(%URI{}), do: :ok
|
|
|
|
def with_underscore({_, _} = _two_tuple), do: :ok
|
|
def with_underscore(_), do: :error
|
|
|
|
def only_underscore(_), do: :ok
|
|
|
|
def two_good_names(first, :ok), do: first
|
|
def two_good_names(second, :error), do: second
|
|
|
|
def really_long_signature(
|
|
really_long_var_named_one,
|
|
really_long_var_named_two,
|
|
really_long_var_named_three
|
|
) do
|
|
{really_long_var_named_one, really_long_var_named_two, really_long_var_named_three}
|
|
end
|
|
end
|
|
)
|
|
|
|
assert {:docs_v1, _, :elixir, _, _, _, docs} = Code.fetch_docs(SignatureDocs)
|
|
signatures = for {{:function, n, a}, _, signature, _, %{}} <- docs, do: {{n, a}, signature}
|
|
|
|
assert [
|
|
arg_names,
|
|
only_underscore,
|
|
really_long_signature,
|
|
two_good_names,
|
|
with_defaults,
|
|
with_map_and_default,
|
|
with_struct,
|
|
with_underscore
|
|
] = Enum.sort(signatures)
|
|
|
|
# arg_names/5
|
|
assert {{:arg_names, 5}, ["arg_names(list1, list2, map1, list3, map2)"]} = arg_names
|
|
|
|
# only_underscore/1
|
|
assert {{:only_underscore, 1}, ["only_underscore(_)"]} = only_underscore
|
|
|
|
# really_long_signature/3
|
|
assert {{:really_long_signature, 3},
|
|
[
|
|
"really_long_signature(really_long_var_named_one, really_long_var_named_two, really_long_var_named_three)"
|
|
]} = really_long_signature
|
|
|
|
# two_good_names/2
|
|
assert {{:two_good_names, 2}, ["two_good_names(first, atom)"]} = two_good_names
|
|
|
|
# with_defaults/4
|
|
assert {{:with_defaults, 4},
|
|
["with_defaults(int, arg \\\\ 0, year \\\\ 2015, fun \\\\ &>=/2)"]} = with_defaults
|
|
|
|
# with_map_and_default/1
|
|
assert {{:with_map_and_default, 1}, ["with_map_and_default(map \\\\ %{key: :default})"]} =
|
|
with_map_and_default
|
|
|
|
# with_struct/1
|
|
assert {{:with_struct, 1}, ["with_struct(uri)"]} = with_struct
|
|
|
|
# with_underscore/1
|
|
assert {{:with_underscore, 1}, ["with_underscore(two_tuple)"]} = with_underscore
|
|
end
|
|
|
|
test "includes docs for functions, modules, types and callbacks" do
|
|
write_beam(
|
|
defmodule SampleDocs do
|
|
@moduledoc "Module doc"
|
|
@moduledoc authors: "Elixir Contributors", purpose: :test
|
|
|
|
@doc "My struct"
|
|
defstruct [:sample]
|
|
|
|
@typedoc "Type doc"
|
|
@typedoc since: "1.2.3", color: :red
|
|
@type foo(any) :: any
|
|
|
|
@typedoc "Opaque type doc"
|
|
@opaque bar(any) :: any
|
|
|
|
@doc "Callback doc"
|
|
@doc since: "1.2.3", color: :red, deprecated: "use baz/2 instead"
|
|
@doc color: :blue, stable: true
|
|
@callback foo(any) :: any
|
|
|
|
@doc false
|
|
@doc since: "1.2.3"
|
|
@callback bar() :: term
|
|
@callback baz(any, term) :: any
|
|
|
|
@doc "Callback with multiple clauses"
|
|
@callback callback_multi(integer) :: integer
|
|
@callback callback_multi(atom) :: atom
|
|
|
|
@doc "Macrocallback doc"
|
|
@macrocallback qux(any) :: any
|
|
|
|
@doc "Macrocallback with multiple clauses"
|
|
@macrocallback macrocallback_multi(integer) :: integer
|
|
@macrocallback macrocallback_multi(atom) :: atom
|
|
|
|
@doc "Function doc"
|
|
@doc since: "1.2.3", color: :red
|
|
@doc color: :blue, stable: true
|
|
@deprecated "use baz/2 instead"
|
|
def foo(arg \\ 0), do: arg + 1
|
|
|
|
@doc "Multiple function head doc"
|
|
@deprecated "something else"
|
|
def bar(_arg)
|
|
def bar(arg), do: arg + 1
|
|
|
|
require Kernel.DocsTest
|
|
Kernel.DocsTest.wrong_doc_baz()
|
|
|
|
@doc "Multiple function head and docs"
|
|
@doc since: "1.2.3"
|
|
def baz(_arg)
|
|
|
|
@doc false
|
|
def qux(true), do: false
|
|
|
|
@doc "A guard"
|
|
defguard is_zero(v) when v == 0
|
|
|
|
# We do this to avoid the deprecation warning.
|
|
module = Module
|
|
module.add_doc(__MODULE__, __ENV__.line, :def, {:nullary, 0}, [], "add_doc")
|
|
def nullary, do: 0
|
|
end
|
|
)
|
|
|
|
assert {:docs_v1, _, :elixir, "text/markdown", %{"en" => module_doc}, module_doc_meta, docs} =
|
|
Code.fetch_docs(SampleDocs)
|
|
|
|
assert module_doc == "Module doc"
|
|
|
|
assert %{authors: "Elixir Contributors", purpose: :test} = module_doc_meta
|
|
|
|
[
|
|
callback_bar,
|
|
callback_baz,
|
|
callback_multi,
|
|
callback_foo,
|
|
function_struct_0,
|
|
function_struct_1,
|
|
function_bar,
|
|
function_baz,
|
|
function_foo,
|
|
function_nullary,
|
|
function_qux,
|
|
guard_is_zero,
|
|
macrocallback_multi,
|
|
macrocallback_qux,
|
|
type_bar,
|
|
type_foo
|
|
] = Enum.sort(docs)
|
|
|
|
assert {{:callback, :bar, 0}, _, [], :hidden, %{}} = callback_bar
|
|
assert {{:callback, :baz, 2}, _, [], :none, %{}} = callback_baz
|
|
|
|
assert {{:callback, :foo, 1}, _, [], %{"en" => "Callback doc"},
|
|
%{since: "1.2.3", deprecated: "use baz/2 instead", color: :blue, stable: true}} =
|
|
callback_foo
|
|
|
|
assert {{:callback, :callback_multi, 1}, _, [], %{"en" => "Callback with multiple clauses"},
|
|
%{}} = callback_multi
|
|
|
|
assert {{:function, :__struct__, 0}, _, ["%Kernel.DocsTest.SampleDocs{}"],
|
|
%{"en" => "My struct"}, %{}} = function_struct_0
|
|
|
|
assert {{:function, :__struct__, 1}, _, ["__struct__(kv)"], :hidden, %{}} =
|
|
function_struct_1
|
|
|
|
assert {{:function, :bar, 1}, _, ["bar(arg)"], %{"en" => "Multiple function head doc"},
|
|
%{deprecated: "something else"}} = function_bar
|
|
|
|
assert {{:function, :baz, 1}, _, ["baz(arg)"], %{"en" => "Multiple function head and docs"},
|
|
%{since: "1.2.3"}} = function_baz
|
|
|
|
assert {{:function, :foo, 1}, _, ["foo(arg \\\\ 0)"], %{"en" => "Function doc"},
|
|
%{
|
|
since: "1.2.3",
|
|
deprecated: "use baz/2 instead",
|
|
color: :blue,
|
|
stable: true,
|
|
defaults: 1
|
|
}} = function_foo
|
|
|
|
assert {{:function, :nullary, 0}, _, ["nullary()"], %{"en" => "add_doc"}, %{}} =
|
|
function_nullary
|
|
|
|
assert {{:function, :qux, 1}, _, ["qux(bool)"], :hidden, %{}} = function_qux
|
|
|
|
assert {{:macro, :is_zero, 1}, _, ["is_zero(v)"], %{"en" => "A guard"}, %{guard: true}} =
|
|
guard_is_zero
|
|
|
|
assert {{:macrocallback, :macrocallback_multi, 1}, _, [],
|
|
%{"en" => "Macrocallback with multiple clauses"}, %{}} = macrocallback_multi
|
|
|
|
assert {{:macrocallback, :qux, 1}, _, [], %{"en" => "Macrocallback doc"}, %{}} =
|
|
macrocallback_qux
|
|
|
|
assert {{:type, :bar, 1}, _, [], %{"en" => "Opaque type doc"}, %{opaque: true}} = type_bar
|
|
|
|
assert {{:type, :foo, 1}, _, [], %{"en" => "Type doc"}, %{since: "1.2.3", color: :red}} =
|
|
type_foo
|
|
end
|
|
end
|
|
|
|
test "fetch docs chunk from doc/chunks" do
|
|
Code.compiler_options(docs: false)
|
|
|
|
doc_chunks_path = Path.join([tmp_path(), "doc", "chunks"])
|
|
File.rm_rf!(doc_chunks_path)
|
|
File.mkdir_p!(doc_chunks_path)
|
|
|
|
write_beam(
|
|
defmodule ExternalDocs do
|
|
end
|
|
)
|
|
|
|
assert Code.fetch_docs(ExternalDocs) == {:error, :chunk_not_found}
|
|
|
|
path = Path.join([doc_chunks_path, "#{ExternalDocs}.chunk"])
|
|
chunk = {:docs_v1, 1, :elixir, "text/markdown", %{"en" => "Some docs"}, %{}}
|
|
File.write!(path, :erlang.term_to_binary(chunk))
|
|
|
|
assert Code.fetch_docs(ExternalDocs) == chunk
|
|
after
|
|
Code.compiler_options(docs: true)
|
|
end
|
|
|
|
test "@impl true doesn't set @doc false if previous implementation has docs" do
|
|
write_beam(
|
|
defmodule Docs do
|
|
defmodule SampleBehaviour do
|
|
@callback foo(any()) :: any()
|
|
@callback bar() :: any()
|
|
@callback baz() :: any()
|
|
end
|
|
|
|
@behaviour SampleBehaviour
|
|
|
|
@doc "Foo docs"
|
|
def foo(nil), do: nil
|
|
|
|
@impl true
|
|
def foo(_), do: false
|
|
|
|
@impl true
|
|
def bar(), do: true
|
|
|
|
@doc "Baz docs"
|
|
@impl true
|
|
def baz(), do: true
|
|
|
|
def fuz(), do: true
|
|
end
|
|
)
|
|
|
|
{:docs_v1, _, _, _, _, _, docs} = Code.fetch_docs(Docs)
|
|
function_docs = for {{:function, name, arity}, _, _, doc, _} <- docs, do: {{name, arity}, doc}
|
|
|
|
assert [
|
|
{{:bar, 0}, :hidden},
|
|
{{:baz, 0}, %{"en" => "Baz docs"}},
|
|
{{:foo, 1}, %{"en" => "Foo docs"}},
|
|
{{:fuz, 0}, :none}
|
|
] = Enum.sort(function_docs)
|
|
end
|
|
|
|
test "generated functions are annotated as such" do
|
|
write_beam(
|
|
defmodule ToBeUsed do
|
|
defmacro __using__(_) do
|
|
quote do
|
|
@doc "Hello"
|
|
def foo, do: :bar
|
|
end
|
|
end
|
|
end
|
|
)
|
|
|
|
write_beam(
|
|
defmodule WillBeUsing do
|
|
use ToBeUsed
|
|
end
|
|
)
|
|
|
|
{:docs_v1, _, _, _, _, _, docs} = Code.fetch_docs(WillBeUsing)
|
|
|
|
assert [
|
|
{{:function, :foo, 0}, [generated: true, location: 399], ["foo()"],
|
|
%{"en" => "Hello"}, %{}}
|
|
] = docs
|
|
end
|
|
end
|