Compare commits
8
Commits
v1.12.1
...
v1.7.0-rc.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1164784b8e | ||
|
|
6694ddbd2f | ||
|
|
85a4d55efb | ||
|
|
9b238e0316 | ||
|
|
ca82388792 | ||
|
|
b33dd12d86 | ||
|
|
c36b2c9070 | ||
|
|
ac75cdbe05 |
+19
-1
@@ -2,6 +2,21 @@
|
||||
|
||||
Elixir v1.7 is the last release to support Erlang/OTP 19. We recommend everyone to migrate to Erlang/OTP 20+.
|
||||
|
||||
## Documentation metadata
|
||||
|
||||
Elixir v1.7 implements [EEP 48](http://erlang.org/eep/eeps/eep-0048.html). EEP 48 aims to bring documentation interoperability across all languages running on the Erlang VM. The documentation format proposed by EEP 48 also supports metadata, which is now fully exposed to Elixir developers:
|
||||
|
||||
```elixir
|
||||
@moduledoc "A brand new module"
|
||||
@moduledoc authors: ["Jane", "Mary"], since: "1.4.0"
|
||||
```
|
||||
|
||||
Currently Elixir supports two metadata keys: `:deprecated` and `:since`. Other keys will be added in the future. Passing metadata is supported on `@doc`, `@moduledoc` and `@typedoc`.
|
||||
|
||||
To access the new documentation, developers should use `Code.fetch_docs/1`. The old documentation format is no longer available and the old `Code.get_docs/1` function will return `nil` accordingly.
|
||||
|
||||
Tools like IEx and ExDoc have been updated to leverage the new format and show relevant metadata to users.
|
||||
|
||||
## The `__STACKTRACE__` construct
|
||||
|
||||
Erlang/OTP 21.0 introduces a new way to retrieve the stacktrace that is lexically scoped and no longer relies on side-effects like `System.stacktrace/0` does. Before one would write:
|
||||
@@ -101,7 +116,7 @@ Percentage | Module
|
||||
77.19% | Total
|
||||
```
|
||||
|
||||
## v1.7.0-dev
|
||||
## v1.7.0-rc.0 (2018-07-13)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -119,6 +134,7 @@ Percentage | Module
|
||||
* [Function] Add `Function` module with `capture/3`, `info/1` and `info/2` functions
|
||||
* [GenServer] Support the new `handle_continue/2` callback on Erlang/OTP 21+
|
||||
* [IO.ANSI] Add cursor movement to `IO.ANSI`
|
||||
* [Kernel] Support adding arbitrary documentation metadata by passing a keyword list to `@doc`, `@moduledoc` and `@typedoc`
|
||||
* [Kernel] Introduce `__STACKTRACE__` to retrieve the current stacktrace inside `catch`/`rescue` (this will be a requirement for Erlang/OTP 21+)
|
||||
* [Kernel] Raise on unsafe variables in order to allow us to better track unused variables
|
||||
* [Kernel] Warn when using `length` to check if a list is not empty on guards
|
||||
@@ -146,6 +162,7 @@ Percentage | Module
|
||||
* [IEx.Helpers] Add `use_if_available/2`
|
||||
* [IEx.Helpers] Allow `force: true` option in `recompile/1`
|
||||
* [IEx.Helpers] Add `:allocators` pane to `runtime_info/1`
|
||||
* [IEx.Helpers] Show documentation metadata in `h/1` helpers
|
||||
|
||||
#### Logger
|
||||
|
||||
@@ -179,6 +196,7 @@ Percentage | Module
|
||||
* [Code] Ensure `string_to_quoted` returns error tuples instead of raising in certain constructs
|
||||
* [Code.Formatter] Consistently format keyword lists in function calls with and without parens
|
||||
* [Code.Formatter] Do not break after `->` when there are only comments and one-line clauses
|
||||
* [File] Allow the `:trim_bom` option to be used with `:encoding`
|
||||
* [Kernel] Raise on unsafe variables as some of the code emitted with unsafe variables would not correctly propagate variables or would disable tail call optimization semantics
|
||||
* [Kernel] Do not crash on dynamic sizes in binary generators with collectable into in comprehensions
|
||||
* [Kernel] Do not crash on literals with non-unary size in binary generators with collectable into in comprehensions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
CANONICAL := master/
|
||||
CANONICAL := v1.7/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
|
||||
@@ -351,12 +351,12 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28)
|
||||
"2015-02-28"
|
||||
iex> Calendar.ISO.date_to_string(2017, 8, 1)
|
||||
"2017-08-01"
|
||||
iex> Calendar.ISO.date_to_string(-99, 1, 31)
|
||||
"-0099-01-31"
|
||||
iex> Calendar.ISO.date_to_string(2015, 2, 28)
|
||||
"2015-02-28"
|
||||
iex> Calendar.ISO.date_to_string(2017, 8, 1)
|
||||
"2017-08-01"
|
||||
iex> Calendar.ISO.date_to_string(-99, 1, 31)
|
||||
"-0099-01-31"
|
||||
|
||||
"""
|
||||
@spec date_to_string(year, month, day) :: String.t()
|
||||
@@ -378,10 +378,10 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
|
||||
"2015-02-28 01:02:03.000004"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
|
||||
"2017-08-01 01:02:03.00000"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 6})
|
||||
"2015-02-28 01:02:03.000004"
|
||||
iex> Calendar.ISO.naive_datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5})
|
||||
"2017-08-01 01:02:03.00000"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@@ -403,14 +403,14 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
|
||||
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
|
||||
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
|
||||
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
|
||||
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CET", 3600, 0)
|
||||
"2017-08-01 01:02:03.00000+01:00 CET Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2017, 8, 1, 1, 2, 3, {4, 5}, "Europe/Berlin", "CDT", 3600, 3600)
|
||||
"2017-08-01 01:02:03.00000+02:00 CDT Europe/Berlin"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PST", -28800, 0)
|
||||
"2015-02-28 01:02:03.00000-08:00 PST America/Los_Angeles"
|
||||
iex> Calendar.ISO.datetime_to_string(2015, 2, 28, 1, 2, 3, {4, 5}, "America/Los_Angeles", "PDT", -28800, 3600)
|
||||
"2015-02-28 01:02:03.00000-07:00 PDT America/Los_Angeles"
|
||||
|
||||
"""
|
||||
@impl true
|
||||
@@ -452,14 +452,14 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 28)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 30)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 31)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 32)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 28)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(2015, 2, 30)
|
||||
false
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 31)
|
||||
true
|
||||
iex> Calendar.ISO.valid_date?(-1, 12, 32)
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@@ -476,12 +476,12 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
|
||||
false
|
||||
iex> Calendar.ISO.valid_time?(10, 50, 25, {3006, 6})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(23, 59, 60, {0, 0})
|
||||
true
|
||||
iex> Calendar.ISO.valid_time?(24, 0, 0, {0, 0})
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
|
||||
@@ -716,14 +716,21 @@ defmodule Code.Formatter do
|
||||
{concat(op_string, doc), @empty, newlines, state}
|
||||
end
|
||||
|
||||
operand_to_algebra_with_comments(
|
||||
operands,
|
||||
meta,
|
||||
min_line,
|
||||
max_line,
|
||||
state,
|
||||
operand_to_algebra
|
||||
)
|
||||
{doc, state} =
|
||||
operand_to_algebra_with_comments(
|
||||
operands,
|
||||
meta,
|
||||
min_line,
|
||||
max_line,
|
||||
state,
|
||||
operand_to_algebra
|
||||
)
|
||||
|
||||
if keyword?(right_arg) and context in [:parens_arg, :no_parens_arg] do
|
||||
{wrap_in_parens(doc), state}
|
||||
else
|
||||
{doc, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp binary_op_to_algebra(op, _, meta, left_arg, right_arg, context, state, _nesting)
|
||||
|
||||
@@ -1512,7 +1512,8 @@ defmodule File do
|
||||
in raw mode for performance reasons. Therefore, Elixir **will** open
|
||||
streams in `:raw` mode with the `:read_ahead` option unless an encoding
|
||||
is specified. This means any data streamed into the file must be
|
||||
converted to `t:iodata/0` type. If you pass `[:utf8]` in the modes parameter,
|
||||
converted to `t:iodata/0` type. If you pass e.g. `[encoding: :utf8]`
|
||||
or `[encoding: {:utf16, :little}]` in the modes parameter,
|
||||
the underlying stream will use `IO.write/2` and the `String.Chars` protocol
|
||||
to convert the data. See `IO.binwrite/2` and `IO.write/2` .
|
||||
|
||||
@@ -1524,6 +1525,9 @@ defmodule File do
|
||||
If you pass `:trim_bom` in the modes parameter, the stream will
|
||||
trim UTF-8, UTF-16 and UTF-32 byte order marks when reading from file.
|
||||
|
||||
Note that this function does not try to discover the file encoding basing
|
||||
on BOM.
|
||||
|
||||
## Examples
|
||||
|
||||
# Read in 2048 byte chunks rather than lines
|
||||
|
||||
@@ -140,12 +140,24 @@ defmodule File.Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_bom(device, raw) do
|
||||
header = if raw, do: IO.binread(device, 4), else: IO.read(device, 1)
|
||||
{:ok, new_pos} = :file.position(device, bom_length(header))
|
||||
defp trim_bom(device, true) do
|
||||
bom_length = device |> IO.binread(4) |> bom_length()
|
||||
{:ok, new_pos} = :file.position(device, bom_length)
|
||||
{device, new_pos}
|
||||
end
|
||||
|
||||
defp trim_bom(device, false) do
|
||||
# Or we read the bom in the correct amount or it isn't there
|
||||
case bom_length(IO.read(device, 1)) do
|
||||
0 ->
|
||||
{:ok, _} = :file.position(device, 0)
|
||||
{device, 0}
|
||||
|
||||
_ ->
|
||||
{device, 1}
|
||||
end
|
||||
end
|
||||
|
||||
defp bom_length(<<239, 187, 191, _rest::binary>>), do: 3
|
||||
defp bom_length(<<254, 255, _rest::binary>>), do: 2
|
||||
defp bom_length(<<255, 254, _rest::binary>>), do: 2
|
||||
|
||||
@@ -7,12 +7,13 @@ defmodule IO.ANSI.Docs do
|
||||
@doc """
|
||||
The default options used by this module.
|
||||
|
||||
The supported values are:
|
||||
The supported keys are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_code` - code blocks (cyan)
|
||||
* `:doc_headings` - h1, h2, h3, h4, h5, h6 headings (yellow)
|
||||
* `:doc_metadata` - documentation metadata keys (yellow)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_table_heading` - the style for table headings
|
||||
* `:doc_title` - top level heading (reverse, yellow)
|
||||
@@ -29,6 +30,7 @@ defmodule IO.ANSI.Docs do
|
||||
doc_bold: [:bright],
|
||||
doc_code: [:cyan],
|
||||
doc_headings: [:yellow],
|
||||
doc_metadata: [:yellow],
|
||||
doc_inline_code: [:cyan],
|
||||
doc_table_heading: [:reverse],
|
||||
doc_title: [:reverse, :yellow],
|
||||
@@ -53,6 +55,39 @@ defmodule IO.ANSI.Docs do
|
||||
newline_after_block()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints documentation metadata (only `since` and `deprecated` for now).
|
||||
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
@spec print_metadata(map, keyword) :: :ok
|
||||
def print_metadata(metadata, options \\ []) when is_map(metadata) do
|
||||
options = Keyword.merge(default_options(), options)
|
||||
print_each_metadata(metadata, options) && IO.write("\n")
|
||||
end
|
||||
|
||||
@metadata_filter [:deprecated, :since]
|
||||
|
||||
defp print_each_metadata(metadata, options) do
|
||||
Enum.reduce(metadata, false, fn
|
||||
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
|
||||
label = metadata_label(key, options)
|
||||
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
|
||||
write_with_wrap([label | String.split(value, @spaces)], options[:width], indent, true)
|
||||
|
||||
_metadata, printed ->
|
||||
printed
|
||||
end)
|
||||
end
|
||||
|
||||
defp metadata_label(key, options) do
|
||||
if options[:enabled] do
|
||||
"#{color(:doc_metadata, options)}#{key}:#{IO.ANSI.reset()}"
|
||||
else
|
||||
"#{key}:"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
|
||||
@@ -136,4 +136,4 @@ Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1
|
||||
[v1.4]: https://github.com/elixir-lang/elixir/blob/v1.4/CHANGELOG.md#4-deprecations
|
||||
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
|
||||
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
|
||||
[v1.7]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.7]: https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md#4-hard-deprecations
|
||||
|
||||
@@ -447,6 +447,20 @@ defmodule Code.Formatter.IntegrationTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "when with keywords inside call" do
|
||||
assert_same """
|
||||
quote((bar(foo(1)) when bat: foo(1)), [])
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
quote(do: (bar(foo(1)) when bat: foo(1)), line: 1)
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
typespec(quote(do: (bar(foo(1)) when bat: foo(1))), [foo: 1], [])
|
||||
"""
|
||||
end
|
||||
|
||||
test "capture with operators" do
|
||||
assert_same """
|
||||
"this works" |> (&String.upcase/1) |> (&String.downcase/1)
|
||||
|
||||
@@ -1569,11 +1569,11 @@ defmodule FileTest do
|
||||
src = fixture_path("utf8_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8])
|
||||
|> File.stream!([{:encoding, :utf8}])
|
||||
|> Enum.take(1) == [<<239, 187, 191>> <> "Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8], 1)
|
||||
|> File.stream!([{:encoding, :utf8}], 1)
|
||||
|> Enum.take(9) == ["\uFEFF", "Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
@@ -1581,11 +1581,51 @@ defmodule FileTest do
|
||||
src = fixture_path("utf8_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8, :trim_bom])
|
||||
|> File.stream!([{:encoding, :utf8}, :trim_bom])
|
||||
|> Enum.take(1) == ["Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([:utf8, :trim_bom], 1)
|
||||
|> File.stream!([{:encoding, :utf8}, :trim_bom], 1)
|
||||
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
test "keeps BOM with UTF16 BE" do
|
||||
src = fixture_path("utf16_be_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :big}}])
|
||||
|> Enum.take(1) == ["\uFEFFРусский\n"]
|
||||
end
|
||||
|
||||
test "keeps BOM with UTF16 LE" do
|
||||
src = fixture_path("utf16_le_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :little}}])
|
||||
|> Enum.take(1) == ["\uFEFFРусский\n"]
|
||||
end
|
||||
|
||||
test "trims BOM via option with utf16 BE encoding" do
|
||||
src = fixture_path("utf16_be_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :big}}, :trim_bom])
|
||||
|> Enum.take(1) == ["Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :big}}, :trim_bom], 1)
|
||||
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
test "trims BOM via option with utf16 LE encoding" do
|
||||
src = fixture_path("utf16_le_bom.txt")
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :little}}, :trim_bom])
|
||||
|> Enum.take(1) == ["Русский\n"]
|
||||
|
||||
assert src
|
||||
|> File.stream!([{:encoding, {:utf16, :little}}, :trim_bom], 1)
|
||||
|> Enum.take(8) == ["Р", "у", "с", "с", "к", "и", "й", "\n"]
|
||||
end
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,10 @@ defmodule IO.ANSI.DocsTest do
|
||||
capture_io(fn -> IO.ANSI.Docs.print_heading(str, []) end) |> String.trim_trailing()
|
||||
end
|
||||
|
||||
def format_metadata(map) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print_metadata(map, []) end)
|
||||
end
|
||||
|
||||
def format(str) do
|
||||
capture_io(fn -> IO.ANSI.Docs.print(str, []) end) |> String.trim_trailing()
|
||||
end
|
||||
@@ -19,6 +23,12 @@ defmodule IO.ANSI.DocsTest do
|
||||
assert String.contains?(result, " wibble ")
|
||||
end
|
||||
|
||||
test "metadata is formatted" do
|
||||
result = format_metadata(%{since: "1.2.3", deprecated: "Use that other one", author: "Alice"})
|
||||
assert result == "\e[33mdeprecated:\e[0m Use that other one\n\e[33msince:\e[0m 1.2.3\n\n"
|
||||
assert format_metadata(%{author: "Alice"}) == ""
|
||||
end
|
||||
|
||||
test "first level heading is converted" do
|
||||
result = format("# wibble\n\ntext\n")
|
||||
assert result == "\e[33m# wibble\e[0m\n\e[0m\ntext\n\e[0m"
|
||||
|
||||
@@ -250,8 +250,8 @@ defmodule IEx.Introspection do
|
||||
case Code.ensure_loaded(module) do
|
||||
{:module, _} ->
|
||||
case Code.fetch_docs(module) do
|
||||
{:docs_v1, _, _, _, %{} = doc, _, _} ->
|
||||
print_doc(inspect(module), [], doc)
|
||||
{:docs_v1, _, _, _, %{} = doc, metadata, _} ->
|
||||
print_doc(inspect(module), [], doc, metadata)
|
||||
|
||||
{:docs_v1, _, _, _, _, _, _} ->
|
||||
docs_not_found(inspect(module))
|
||||
@@ -376,7 +376,7 @@ defmodule IEx.Introspection do
|
||||
|
||||
is_nil(docs) and spec != [] ->
|
||||
message = %{"en" => "Module was compiled without docs. Showing only specs."}
|
||||
print_doc("#{inspect(mod)}.#{fun}/#{arity}", spec, message)
|
||||
print_doc("#{inspect(mod)}.#{fun}/#{arity}", spec, message, %{})
|
||||
:ok
|
||||
|
||||
is_nil(docs) ->
|
||||
@@ -449,7 +449,7 @@ defmodule IEx.Introspection do
|
||||
defp has_content?({{_, name, _}, _, _, :none, _}), do: hd(Atom.to_charlist(name)) != ?_
|
||||
defp has_content?({_, _, _, _, _}), do: true
|
||||
|
||||
defp print_fun(mod, {{kind, fun, arity}, _line, signature, doc, _meta}, spec) do
|
||||
defp print_fun(mod, {{kind, fun, arity}, _line, signature, doc, metadata}, spec) do
|
||||
if callback_module = doc == :none and callback_module(mod, fun, arity) do
|
||||
filter = &match?({_, ^fun, ^arity}, elem(&1, 0))
|
||||
|
||||
@@ -458,7 +458,7 @@ defmodule IEx.Introspection do
|
||||
_ -> nil
|
||||
end
|
||||
else
|
||||
print_doc("#{kind_to_def(kind)} #{Enum.join(signature, " ")}", spec, doc)
|
||||
print_doc("#{kind_to_def(kind)} #{Enum.join(signature, " ")}", spec, doc, metadata)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -513,7 +513,7 @@ defmodule IEx.Introspection do
|
||||
{:ok, docs} ->
|
||||
docs
|
||||
|> add_optional_callback_docs(mod)
|
||||
|> Enum.each(fn {definition, _} -> IO.puts(definition) end)
|
||||
|> Enum.each(fn {definition, _, _} -> IO.puts(definition) end)
|
||||
end
|
||||
|
||||
dont_display_result()
|
||||
@@ -565,12 +565,12 @@ defmodule IEx.Introspection do
|
||||
docs
|
||||
|> Enum.filter(filter)
|
||||
|> Enum.map(fn
|
||||
{{:macrocallback, fun, arity}, _, _, doc, _} ->
|
||||
{{:macrocallback, fun, arity}, _, _, doc, metadata} ->
|
||||
macro = {:"MACRO-#{fun}", arity + 1}
|
||||
{format_callback(:macrocallback, fun, macro, callbacks), doc}
|
||||
{format_callback(:macrocallback, fun, macro, callbacks), doc, metadata}
|
||||
|
||||
{{kind, fun, arity}, _, _, doc, _} ->
|
||||
{format_callback(kind, fun, {fun, arity}, callbacks), doc}
|
||||
{{kind, fun, arity}, _, _, doc, metadata} ->
|
||||
{format_callback(kind, fun, {fun, arity}, callbacks), doc, metadata}
|
||||
end)
|
||||
|
||||
{:ok, docs}
|
||||
@@ -588,7 +588,7 @@ defmodule IEx.Introspection do
|
||||
if optional_callbacks == [] do
|
||||
docs
|
||||
else
|
||||
docs ++ [{format_optional_callbacks(optional_callbacks), ""}]
|
||||
docs ++ [{format_optional_callbacks(optional_callbacks), "", %{}}]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -637,8 +637,8 @@ defmodule IEx.Introspection do
|
||||
{:ok, types} ->
|
||||
printed =
|
||||
for {_, {^type, _, args}} = typespec <- types do
|
||||
doc = {format_type(typespec), type_doc(module, type, length(args))}
|
||||
print_typespec(doc)
|
||||
type_doc(module, type, length(args), typespec)
|
||||
|> print_typespec()
|
||||
end
|
||||
|
||||
if printed == [] do
|
||||
@@ -657,8 +657,8 @@ defmodule IEx.Introspection do
|
||||
{:ok, types} ->
|
||||
printed =
|
||||
for {_, {^type, _, args}} = typespec <- types, length(args) == arity do
|
||||
doc = {format_type(typespec), type_doc(module, type, arity)}
|
||||
print_typespec(doc)
|
||||
type_doc(module, type, arity, typespec)
|
||||
|> print_typespec()
|
||||
end
|
||||
|
||||
if printed == [] do
|
||||
@@ -674,12 +674,12 @@ defmodule IEx.Introspection do
|
||||
dont_display_result()
|
||||
end
|
||||
|
||||
defp type_doc(module, type, arity) do
|
||||
defp type_doc(module, type, arity, typespec) do
|
||||
if docs = get_docs(module, [:type]) do
|
||||
{_, _, _, content, _} = Enum.find(docs, &match?({:type, ^type, ^arity}, elem(&1, 0)))
|
||||
content
|
||||
{_, _, _, content, metadata} = Enum.find(docs, &match?({:type, ^type, ^arity}, elem(&1, 0)))
|
||||
{format_type(typespec), content, metadata}
|
||||
else
|
||||
:none
|
||||
{format_type(typespec), :none, %{}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -717,27 +717,31 @@ defmodule IEx.Introspection do
|
||||
IEx.color(:doc_inline_code, left) <> " " <> right
|
||||
end
|
||||
|
||||
defp print_doc(heading, types, doc) do
|
||||
defp print_doc(heading, types, doc, metadata) do
|
||||
doc = translate_doc(doc) || ""
|
||||
|
||||
if opts = IEx.Config.ansi_docs() do
|
||||
IO.ANSI.Docs.print_heading(heading, opts)
|
||||
IO.write(types)
|
||||
IO.ANSI.Docs.print_metadata(metadata, opts)
|
||||
IO.ANSI.Docs.print(doc, opts)
|
||||
else
|
||||
IO.puts("* #{heading}\n")
|
||||
IO.write(types)
|
||||
IO.ANSI.Docs.print_metadata(metadata, enabled: false)
|
||||
IO.puts(doc)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_typespec({types, doc}) do
|
||||
defp print_typespec({types, doc, metadata}) do
|
||||
IO.puts(types)
|
||||
doc = translate_doc(doc)
|
||||
|
||||
if opts = IEx.Config.ansi_docs() do
|
||||
IO.ANSI.Docs.print_metadata(metadata, opts)
|
||||
doc && IO.ANSI.Docs.print(doc, opts)
|
||||
else
|
||||
IO.ANSI.Docs.print_metadata(metadata, enabled: false)
|
||||
doc && IO.puts(doc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -403,6 +403,38 @@ defmodule IEx.HelpersTest do
|
||||
"No documentation for Kernel.__info__ was found\n"
|
||||
end
|
||||
|
||||
test "prints documentation metadata" do
|
||||
content = """
|
||||
defmodule Sample do
|
||||
@moduledoc "Sample module"
|
||||
@moduledoc deprecated: "Use OtherSample", since: "1.2.3", authors: ["Alice", "Bob"]
|
||||
@doc "With metadata"
|
||||
@doc since: "1.2.3", author: "Alice"
|
||||
@deprecated "Use OtherSample.with_metadata/0"
|
||||
def with_metadata(), do: 0
|
||||
@doc "Without metadata"
|
||||
def without_metadata(), do: 1
|
||||
end
|
||||
"""
|
||||
|
||||
filename = "sample.ex"
|
||||
|
||||
with_file(filename, content, fn ->
|
||||
assert c(filename, ".") == [Sample]
|
||||
|
||||
assert capture_io(fn -> h(Sample) end) ==
|
||||
"* Sample\n\ndeprecated: Use OtherSample\nsince: 1.2.3\n\nSample module\n"
|
||||
|
||||
assert capture_io(fn -> h(Sample.with_metadata()) end) ==
|
||||
"* def with_metadata()\n\ndeprecated: Use OtherSample.with_metadata/0\nsince: 1.2.3\n\nWith metadata\n"
|
||||
|
||||
assert capture_io(fn -> h(Sample.without_metadata()) end) ==
|
||||
"* def without_metadata()\n\nWithout metadata\n"
|
||||
end)
|
||||
after
|
||||
cleanup_modules([Sample])
|
||||
end
|
||||
|
||||
test "considers underscored functions without docs by default" do
|
||||
content = """
|
||||
defmodule Sample do
|
||||
@@ -593,6 +625,27 @@ defmodule IEx.HelpersTest do
|
||||
"@callback message(t()) :: String.t()\n\n"
|
||||
end
|
||||
|
||||
test "prints callback documentation metadata" do
|
||||
filename = "callback_with_metadata.ex"
|
||||
|
||||
content = """
|
||||
defmodule CallbackWithMetadata do
|
||||
@doc "callback"
|
||||
@doc since: "1.2.3", deprecated: "Use handle_test/1", purpose: :test
|
||||
@callback test(:foo) :: integer
|
||||
end
|
||||
"""
|
||||
|
||||
with_file(filename, content, fn ->
|
||||
assert c(filename, ".") == [CallbackWithMetadata]
|
||||
|
||||
assert capture_io(fn -> b(CallbackWithMetadata.test()) end) ==
|
||||
"@callback test(:foo) :: integer()\n\ndeprecated: Use handle_test/1\nsince: 1.2.3\n\ncallback\n"
|
||||
end)
|
||||
after
|
||||
cleanup_modules([CallbackWithMetadata])
|
||||
end
|
||||
|
||||
test "prints optional callback" do
|
||||
filename = "optional_callbacks.ex"
|
||||
|
||||
@@ -666,6 +719,33 @@ defmodule IEx.HelpersTest do
|
||||
after
|
||||
cleanup_modules([TypeSample])
|
||||
end
|
||||
|
||||
test "prints type documentation metadata" do
|
||||
content = """
|
||||
defmodule TypeSample do
|
||||
@typedoc "An id with description."
|
||||
@typedoc since: "1.2.3", deprecated: "Use t/0", purpose: :test
|
||||
@type id_with_desc :: {number, String.t}
|
||||
end
|
||||
"""
|
||||
|
||||
filename = "typesample.ex"
|
||||
|
||||
with_file(filename, content, fn ->
|
||||
assert c(filename, ".") == [TypeSample]
|
||||
|
||||
assert capture_io(fn -> t(TypeSample.id_with_desc()) end) == """
|
||||
@type id_with_desc() :: {number(), String.t()}
|
||||
|
||||
deprecated: Use t/0
|
||||
since: 1.2.3
|
||||
|
||||
An id with description.
|
||||
"""
|
||||
end)
|
||||
after
|
||||
cleanup_modules([TypeSample])
|
||||
end
|
||||
end
|
||||
|
||||
describe "v" do
|
||||
|
||||
@@ -339,6 +339,10 @@ defmodule Logger.Translator do
|
||||
["\nStart Module: ", inspect(mod) | child_debug(min_level, debug)]
|
||||
end
|
||||
|
||||
defp child_info(_min_level, _child) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp child_debug(:debug, restart_type: restart, shutdown: shutdown, child_type: type) do
|
||||
["\nRestart: ", inspect(restart), "\nShutdown: ", inspect(shutdown)] ++
|
||||
["\nType: ", inspect(type)]
|
||||
|
||||
Reference in New Issue
Block a user