Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9f1be07ca | ||
|
|
f4b6375125 | ||
|
|
ccc24ea5b2 | ||
|
|
65f1b2df1e | ||
|
|
479e46fc7e | ||
|
|
4f634bd244 | ||
|
|
8e1ebafebc | ||
|
|
a89e656a65 | ||
|
|
e615b3dd0e | ||
|
|
6586e19a22 | ||
|
|
8291c2f795 | ||
|
|
f663b2e0de | ||
|
|
e6c893cf89 | ||
|
|
705a637acf | ||
|
|
6b74116cb0 | ||
|
|
9565310fb8 | ||
|
|
718123157c | ||
|
|
be46a1f999 | ||
|
|
0e8de11541 | ||
|
|
f6b0855d31 | ||
|
|
8e6cc2c74d | ||
|
|
db5c1557bb | ||
|
|
984495fd90 | ||
|
|
a0d5c4f4fb | ||
|
|
510ac444e3 | ||
|
|
e13c944ba4 | ||
|
|
6a0e9390a2 | ||
|
|
d59c801f33 | ||
|
|
c107a2fe26 | ||
|
|
17853e63d8 | ||
|
|
338e242a59 | ||
|
|
2de4680c6e | ||
|
|
1cffb06981 | ||
|
|
e0f1ba28dd | ||
|
|
45c7f828ef | ||
|
|
9169b6e302 | ||
|
|
c2bdc66b9e | ||
|
|
d16f354a3b | ||
|
|
dd30768354 | ||
|
|
bf1d993b2f | ||
|
|
755639f538 | ||
|
|
97e0b1e1ff |
@@ -123,6 +123,52 @@ Those improvements will help developers better understand the relationship betwe
|
||||
|
||||
Other improvements in Mix include better compiler diagnostics for editor integration, support for the `--slowest N` flag in `mix test` that shows the slowest tests in your suite, and a new `mix profile.eprof` task that provides time based profiling, complementing the existing `mix profile.cprof` (count based) and `mix profile.fprof` (flame based).
|
||||
|
||||
## v1.6.5 (2018-05-07)
|
||||
|
||||
This release supports Erlang/OTP 21.0-rc by removing all warnings and by properly redirecting logger output. Note it is not guaranteed it will support Erlang/OTP 21.0 final.
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
* [Code] Preserve the user's choice in the formatter on parens call with next break fits
|
||||
* [Code] Preserve the user's choice in the formatter on calls without parens when we have one argument per line
|
||||
* [Code] Fix formatting when there is a tilde in the first element of a bitstring
|
||||
* [Kernel] Support specsdiff flag on `__info__` spec clauses
|
||||
* [Kernel] Do not exclude hygienic vars in `defguard`
|
||||
* [Kernel.SpecialForms] Mark `for` comprehensions as generated to avoid dialyzer warnings
|
||||
* [Macro] Make sure `Macro.to_string/2` emits valid quoted expressions
|
||||
* [Task] Support `:infinity` timeout on `Task.yield_many/2`
|
||||
* [Task.Supervisor] Do not crash spawning supervised tasks when the parent process is dead
|
||||
* [URI] Fix parsing of URIs with trailing `?`
|
||||
|
||||
## v1.6.4 (2018-03-16)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code.Formatter] Do not double escape quoted keyword list identifiers
|
||||
* [Kernel] Properly support `into: binary` in Erlang/OTP 20.3
|
||||
|
||||
## v1.6.3 (2018-03-09)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code.Formatter] Support comments in the middle of pipelines, `when` and `|` expressions
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code.Formatter] Consider commas when breaking groups
|
||||
* [Code.Formatter] Ensure proper precedence between `&` and operators
|
||||
* [Code.Formatter] Consider `.formatter.exs` when formatting stdin
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger.Translator] Ensure logger doesn't crash when reporting named `DynamicSupervisor`
|
||||
|
||||
## v1.6.2 (2018-02-28)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -343,14 +343,14 @@ defmodule Code do
|
||||
|
||||
[foo, bar]
|
||||
|
||||
You can also force keywords to be rendered on multiple lines by
|
||||
having each entry on its own line:
|
||||
You can also force function calls and keywords to be rendered on multiple
|
||||
lines by having each entry on its own line:
|
||||
|
||||
defstruct name: nil,
|
||||
age: 0
|
||||
|
||||
The code above will be kept with one keyword entry per line by the
|
||||
formatter. To avoid that, just keep everything on a single line.
|
||||
formatter. To avoid that, just squash everything into a single line.
|
||||
|
||||
### Parens and no parens in function calls
|
||||
|
||||
|
||||
+226
-127
@@ -10,6 +10,7 @@ defmodule Code.Formatter do
|
||||
@min_line 0
|
||||
@max_line 9_999_999
|
||||
@empty empty()
|
||||
@ampersand_prec Code.Identifier.unary_op(:&) |> elem(1)
|
||||
|
||||
# Operators that do not have space between operands
|
||||
@no_space_binary_operators [:..]
|
||||
@@ -17,8 +18,8 @@ defmodule Code.Formatter do
|
||||
# Operators that do not have newline between operands (as well as => and keywords)
|
||||
@no_newline_binary_operators [:\\, :in]
|
||||
|
||||
# Left associative operators that start on the next line in case of breaks
|
||||
@left_new_line_before_binary_operators [:|>, :~>>, :<<~, :~>, :<~, :<~>, :<|>]
|
||||
# Left associative operators that start on the next line in case of breaks (always pipes)
|
||||
@pipeline_operators [:|>, :~>>, :<<~, :~>, :<~, :<~>, :<|>]
|
||||
|
||||
# Right associative operators that start on the next line in case of breaks
|
||||
@right_new_line_before_binary_operators [:|, :when]
|
||||
@@ -253,6 +254,7 @@ defmodule Code.Formatter do
|
||||
|> Keyword.get(:locals_without_parens, [])
|
||||
|> MapSet.new()
|
||||
|> MapSet.union(@locals_without_parens)
|
||||
|> MapSet.to_list()
|
||||
|
||||
%{
|
||||
locals_without_parens: locals_without_parens,
|
||||
@@ -325,12 +327,12 @@ defmodule Code.Formatter do
|
||||
|
||||
# Special AST nodes from compiler feedback.
|
||||
|
||||
defp quoted_to_algebra({:special, :clause_args, [args]}, _context, state) do
|
||||
defp quoted_to_algebra({{:special, :clause_args}, _meta, [args]}, _context, state) do
|
||||
{doc, state} = clause_args_to_algebra(args, state)
|
||||
{group(doc), state}
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({:special, :bitstring_segment, [arg, last]}, _context, state) do
|
||||
defp quoted_to_algebra({{:special, :bitstring_segment}, _meta, [arg, last]}, _context, state) do
|
||||
bitstring_segment_to_algebra({arg, -1}, state, last)
|
||||
end
|
||||
|
||||
@@ -418,11 +420,11 @@ defmodule Code.Formatter do
|
||||
# {}
|
||||
# {1, 2}
|
||||
defp quoted_to_algebra({:{}, meta, args}, _context, state) do
|
||||
tuple_to_algebra(meta, args, :flex_glue, state)
|
||||
tuple_to_algebra(meta, args, :flex_break, state)
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({:__block__, meta, [{left, right}]}, _context, state) do
|
||||
tuple_to_algebra(meta, [left, right], :flex_glue, state)
|
||||
tuple_to_algebra(meta, [left, right], :flex_break, state)
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({:__block__, meta, [list]}, _context, state) when is_list(list) do
|
||||
@@ -553,7 +555,16 @@ defmodule Code.Formatter do
|
||||
{left, state} =
|
||||
case left_arg do
|
||||
{:__block__, _, [atom]} when is_atom(atom) ->
|
||||
{atom |> Code.Identifier.inspect_as_key() |> string(), state}
|
||||
key =
|
||||
case Code.Identifier.classify(atom) do
|
||||
type when type in [:callable_local, :callable_operator, :not_callable] ->
|
||||
IO.iodata_to_binary([Atom.to_string(atom), ?:])
|
||||
|
||||
_ ->
|
||||
IO.iodata_to_binary([?", Atom.to_string(atom), ?", ?:])
|
||||
end
|
||||
|
||||
{string(key), state}
|
||||
|
||||
{{:., _, [:erlang, :binary_to_atom]}, _, [{:<<>>, _, entries}, :utf8]} ->
|
||||
interpolation_to_algebra(entries, @double_quote, state, "\"", "\":")
|
||||
@@ -670,23 +681,75 @@ defmodule Code.Formatter do
|
||||
# strict or flex mode around.
|
||||
defp binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state) do
|
||||
%{operand_nesting: nesting} = state
|
||||
binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, nil, nesting)
|
||||
binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, nesting)
|
||||
end
|
||||
|
||||
defp binary_op_to_algebra(
|
||||
op,
|
||||
op_string,
|
||||
meta,
|
||||
left_arg,
|
||||
right_arg,
|
||||
context,
|
||||
state,
|
||||
parent_info,
|
||||
nesting
|
||||
) do
|
||||
defp binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, _nesting)
|
||||
when op in @right_new_line_before_binary_operators do
|
||||
op_info = Code.Identifier.binary_op(op)
|
||||
left_context = force_many_args_or_operand(context, :parens_arg)
|
||||
right_context = force_many_args_or_operand(context, :operand)
|
||||
op_string = op_string <> " "
|
||||
left_context = left_op_context(context)
|
||||
right_context = right_op_context(context)
|
||||
|
||||
min_line =
|
||||
case left_arg do
|
||||
{_, left_meta, _} -> line(left_meta)
|
||||
_ -> line(meta)
|
||||
end
|
||||
|
||||
{operands, max_line} =
|
||||
unwrap_right(right_arg, op, meta, right_context, [{{:root, left_context}, left_arg}])
|
||||
|
||||
operand_to_algebra = fn
|
||||
{{:root, context}, arg}, _args, newlines, state ->
|
||||
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, :left, 2)
|
||||
{doc, @empty, newlines, state}
|
||||
|
||||
{{kind, context}, arg}, _args, newlines, state ->
|
||||
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, kind, 0)
|
||||
doc = doc |> nest_by_length(op_string) |> force_keyword(arg)
|
||||
{concat(op_string, doc), @empty, newlines, state}
|
||||
end
|
||||
|
||||
operand_to_algebra_with_comments(
|
||||
operands,
|
||||
meta,
|
||||
min_line,
|
||||
max_line,
|
||||
state,
|
||||
operand_to_algebra
|
||||
)
|
||||
end
|
||||
|
||||
defp binary_op_to_algebra(op, _, meta, left_arg, right_arg, context, state, _nesting)
|
||||
when op in @pipeline_operators do
|
||||
op_info = Code.Identifier.binary_op(op)
|
||||
left_context = left_op_context(context)
|
||||
right_context = right_op_context(context)
|
||||
max_line = line(meta)
|
||||
|
||||
{pipes, min_line} =
|
||||
unwrap_pipes(left_arg, meta, left_context, [{{op, right_context}, right_arg}])
|
||||
|
||||
operand_to_algebra = fn
|
||||
{{:root, context}, arg}, _args, newlines, state ->
|
||||
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, :left, 2)
|
||||
{doc, @empty, newlines, state}
|
||||
|
||||
{{op, context}, arg}, _args, newlines, state ->
|
||||
op_info = Code.Identifier.binary_op(op)
|
||||
op_string = Atom.to_string(op) <> " "
|
||||
{doc, state} = binary_operand_to_algebra(arg, context, state, op, op_info, :right, 0)
|
||||
{concat(op_string, doc), @empty, newlines, state}
|
||||
end
|
||||
|
||||
operand_to_algebra_with_comments(pipes, meta, min_line, max_line, state, operand_to_algebra)
|
||||
end
|
||||
|
||||
defp binary_op_to_algebra(op, op_string, meta, left_arg, right_arg, context, state, nesting) do
|
||||
op_info = Code.Identifier.binary_op(op)
|
||||
left_context = left_op_context(context)
|
||||
right_context = right_op_context(context)
|
||||
|
||||
{left, state} =
|
||||
binary_operand_to_algebra(left_arg, left_context, state, op, op_info, :left, 2)
|
||||
@@ -703,39 +766,6 @@ defmodule Code.Formatter do
|
||||
op_string = " " <> op_string <> " "
|
||||
concat(concat(group(left), op_string), group(right))
|
||||
|
||||
op in @left_new_line_before_binary_operators ->
|
||||
op_string = op_string <> " "
|
||||
|
||||
# If the parent is of the same type (computed via same precedence),
|
||||
# we cannot group the left side yet.
|
||||
left = if op_info == parent_info, do: left, else: group(left)
|
||||
|
||||
doc = glue(left, concat(op_string, group(right)))
|
||||
if Keyword.get(meta, :eol, false), do: force_unfit(doc), else: doc
|
||||
|
||||
op in @right_new_line_before_binary_operators ->
|
||||
op_string = op_string <> " "
|
||||
|
||||
# If the parent is of the same type (computed via same precedence),
|
||||
# we need to nest the left side because of the associativity.
|
||||
left =
|
||||
if op_info == parent_info do
|
||||
nest_by_length(left, op_string)
|
||||
else
|
||||
group(left)
|
||||
end
|
||||
|
||||
# If the right side is of the same type, we will keep recursing
|
||||
# and do the nesting on the left side later on (as written above).
|
||||
right =
|
||||
case right_arg do
|
||||
{^op, _, [_, _]} -> right
|
||||
_ -> right |> nest_by_length(op_string) |> force_keyword(right_arg) |> group()
|
||||
end
|
||||
|
||||
doc = glue(left, concat(op_string, right))
|
||||
if Keyword.get(meta, :eol, false), do: force_unfit(doc), else: doc
|
||||
|
||||
true ->
|
||||
next_break_fits? =
|
||||
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and
|
||||
@@ -769,10 +799,11 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp binary_operand_to_algebra(operand, context, state, parent_op, parent_info, side, nesting) do
|
||||
{parent_assoc, parent_prec} = parent_info
|
||||
|
||||
with {op, meta, [left, right]} <- operand,
|
||||
op_info = Code.Identifier.binary_op(op),
|
||||
{_assoc, prec} <- op_info do
|
||||
{parent_assoc, parent_prec} = parent_info
|
||||
op_string = Atom.to_string(op)
|
||||
|
||||
cond do
|
||||
@@ -780,17 +811,7 @@ defmodule Code.Formatter do
|
||||
# the correct side, we respect the nesting rule to avoid multiple
|
||||
# nestings. This only applies for left associativity or same operator.
|
||||
parent_prec == prec and parent_assoc == side and (side == :left or op == parent_op) ->
|
||||
binary_op_to_algebra(
|
||||
op,
|
||||
op_string,
|
||||
meta,
|
||||
left,
|
||||
right,
|
||||
context,
|
||||
state,
|
||||
parent_info,
|
||||
nesting
|
||||
)
|
||||
binary_op_to_algebra(op, op_string, meta, left, right, context, state, nesting)
|
||||
|
||||
# If the parent requires parens or the precedence is inverted or
|
||||
# it is in the wrong side, then we *need* parenthesis.
|
||||
@@ -799,16 +820,18 @@ defmodule Code.Formatter do
|
||||
parent_op in @required_parens_logical_binary_operands) or parent_prec > prec or
|
||||
(parent_prec == prec and parent_assoc != side) ->
|
||||
{operand, state} =
|
||||
binary_op_to_algebra(op, op_string, meta, left, right, context, state, parent_info, 2)
|
||||
binary_op_to_algebra(op, op_string, meta, left, right, context, state, 2)
|
||||
|
||||
{wrap_in_parens(operand), state}
|
||||
|
||||
# Otherwise, we rely on precedence but also nest.
|
||||
true ->
|
||||
binary_op_to_algebra(op, op_string, meta, left, right, context, state, parent_info, 2)
|
||||
binary_op_to_algebra(op, op_string, meta, left, right, context, state, 2)
|
||||
end
|
||||
else
|
||||
{:&, _, [arg]} when not is_integer(arg) and side == :left ->
|
||||
{:&, _, [arg]}
|
||||
when not is_integer(arg) and side == :left
|
||||
when not is_integer(arg) and parent_assoc == :left and parent_prec > @ampersand_prec ->
|
||||
{doc, state} = quoted_to_algebra(operand, context, state)
|
||||
{wrap_in_parens(doc), state}
|
||||
|
||||
@@ -817,6 +840,45 @@ defmodule Code.Formatter do
|
||||
end
|
||||
end
|
||||
|
||||
defp unwrap_pipes({op, meta, [left, right]}, _meta, context, acc)
|
||||
when op in @pipeline_operators do
|
||||
left_context = left_op_context(context)
|
||||
right_context = right_op_context(context)
|
||||
unwrap_pipes(left, meta, left_context, [{{op, right_context}, right} | acc])
|
||||
end
|
||||
|
||||
defp unwrap_pipes(left, meta, context, acc) do
|
||||
min_line =
|
||||
case left do
|
||||
{_, meta, _} -> line(meta)
|
||||
_ -> line(meta)
|
||||
end
|
||||
|
||||
{[{{:root, context}, left} | acc], min_line}
|
||||
end
|
||||
|
||||
defp unwrap_right({op, meta, [left, right]}, op, _meta, context, acc) do
|
||||
left_context = left_op_context(context)
|
||||
right_context = right_op_context(context)
|
||||
unwrap_right(right, op, meta, right_context, [{{:left, left_context}, left} | acc])
|
||||
end
|
||||
|
||||
defp unwrap_right(right, _op, meta, context, acc) do
|
||||
acc = [{{:right, context}, right} | acc]
|
||||
{Enum.reverse(acc), line(meta)}
|
||||
end
|
||||
|
||||
defp operand_to_algebra_with_comments(operands, meta, min_line, max_line, state, fun) do
|
||||
{docs, comments?, state} =
|
||||
quoted_to_algebra_with_comments(operands, [], min_line, max_line, 1, state, fun)
|
||||
|
||||
if comments? or Keyword.get(meta, :eol, false) do
|
||||
{docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), state}
|
||||
else
|
||||
{docs |> Enum.reduce(&glue(&2, &1)), state}
|
||||
end
|
||||
end
|
||||
|
||||
## Module attributes
|
||||
|
||||
# @Foo
|
||||
@@ -898,7 +960,7 @@ defmodule Code.Formatter do
|
||||
# expression.{arguments}
|
||||
defp remote_to_algebra({{:., _, [target, :{}]}, meta, args}, _context, state) do
|
||||
{target_doc, state} = remote_target_to_algebra(target, state)
|
||||
{call_doc, state} = tuple_to_algebra(meta, args, :glue, state)
|
||||
{call_doc, state} = tuple_to_algebra(meta, args, :break, state)
|
||||
{concat(concat(target_doc, "."), call_doc), state}
|
||||
end
|
||||
|
||||
@@ -1025,7 +1087,7 @@ defmodule Code.Formatter do
|
||||
#
|
||||
defp call_args_to_algebra([], meta, _context, _parens, _list_to_keyword?, state) do
|
||||
{args_doc, _join, state} =
|
||||
args_to_algebra_with_comments([], meta, false, false, :glue, state, &{&1, &2})
|
||||
args_to_algebra_with_comments([], meta, false, :none, :break, state, &{&1, &2})
|
||||
|
||||
{{surround("(", args_doc, ")"), state}, false}
|
||||
end
|
||||
@@ -1059,7 +1121,6 @@ defmodule Code.Formatter do
|
||||
|
||||
defp call_args_to_algebra_no_blocks(meta, args, skip_parens?, list_to_keyword?, extra, state) do
|
||||
{left, right} = split_last(args)
|
||||
generators_count = count_generators(args)
|
||||
{keyword?, right} = last_arg_to_keyword(right, list_to_keyword?)
|
||||
|
||||
context =
|
||||
@@ -1069,21 +1130,22 @@ defmodule Code.Formatter do
|
||||
if skip_parens?, do: :no_parens_arg, else: :parens_arg
|
||||
end
|
||||
|
||||
if left != [] and keyword? and skip_parens? and generators_count == 0 do
|
||||
if left != [] and keyword? and skip_parens? and no_generators?(args) do
|
||||
call_args_to_algebra_with_no_parens_keywords(meta, left, right, context, extra, state)
|
||||
else
|
||||
next_break_fits? = next_break_fits?(right, state)
|
||||
force_keyword? = keyword? and force_keyword?(right)
|
||||
non_empty_eol? = left != [] and not next_break_fits? and Keyword.get(meta, :eol, false)
|
||||
join = if generators_count > 1 or force_keyword? or non_empty_eol?, do: :line, else: :glue
|
||||
args = if keyword?, do: left ++ right, else: left ++ [right]
|
||||
many_eol? = match?([_, _ | _], args) and Keyword.get(meta, :eol, false)
|
||||
join = if force_args?(args) or many_eol?, do: :line, else: :break
|
||||
|
||||
next_break_fits? = join == :break and next_break_fits?(right, state)
|
||||
last_arg_mode = if next_break_fits?, do: :next_break_fits, else: :none
|
||||
|
||||
{args_doc, _join, state} =
|
||||
args_to_algebra_with_comments(
|
||||
args,
|
||||
meta,
|
||||
skip_parens?,
|
||||
next_break_fits?,
|
||||
last_arg_mode,
|
||||
join,
|
||||
state,
|
||||
"ed_to_algebra(&1, context, &2)
|
||||
@@ -1117,14 +1179,17 @@ defmodule Code.Formatter do
|
||||
|
||||
defp call_args_to_algebra_with_no_parens_keywords(meta, left, right, context, extra, state) do
|
||||
to_algebra_fun = "ed_to_algebra(&1, context, &2)
|
||||
join = if force_args?(left), do: :line, else: :break
|
||||
|
||||
{left_doc, _join, state} =
|
||||
args_to_algebra_with_comments(left, meta, true, false, :glue, state, to_algebra_fun)
|
||||
args_to_algebra_with_comments(left, meta, true, :force_comma, join, state, to_algebra_fun)
|
||||
|
||||
join = if force_args?(right) or force_args?(left ++ right), do: :line, else: :break
|
||||
|
||||
{right_doc, _join, state} =
|
||||
args_to_algebra_with_comments(right, meta, false, false, :glue, state, to_algebra_fun)
|
||||
args_to_algebra_with_comments(right, meta, false, :none, join, state, to_algebra_fun)
|
||||
|
||||
right_doc = "," |> glue(right_doc) |> force_keyword(right) |> group(:inherit)
|
||||
right_doc = apply(Inspect.Algebra, join, []) |> concat(right_doc) |> group(:inherit)
|
||||
|
||||
doc =
|
||||
with_next_break_fits(true, right_doc, fn right_doc ->
|
||||
@@ -1149,8 +1214,8 @@ defmodule Code.Formatter do
|
||||
end)
|
||||
end
|
||||
|
||||
defp count_generators(args) do
|
||||
Enum.count(args, &match?({:<-, _, [_, _]}, &1))
|
||||
defp no_generators?(args) do
|
||||
not Enum.any?(args, &match?({:<-, _, [_, _]}, &1))
|
||||
end
|
||||
|
||||
defp do_end_blocks([{{:__block__, meta, [:do]}, _} | _] = blocks) do
|
||||
@@ -1262,15 +1327,15 @@ defmodule Code.Formatter do
|
||||
|
||||
defp bitstring_to_algebra(meta, args, state) do
|
||||
last = length(args) - 1
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :flex_glue
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :flex_break
|
||||
to_algebra_fun = &bitstring_segment_to_algebra(&1, &2, last)
|
||||
|
||||
{args_doc, join, state} =
|
||||
args
|
||||
|> Enum.with_index()
|
||||
|> args_to_algebra_with_comments(meta, false, false, join, state, to_algebra_fun)
|
||||
|> args_to_algebra_with_comments(meta, false, :none, join, state, to_algebra_fun)
|
||||
|
||||
if join == :flex_glue do
|
||||
if join == :flex_break do
|
||||
{"<<" |> concat(args_doc) |> nest(2) |> concat(">>") |> group(), state}
|
||||
else
|
||||
{surround("<<", args_doc, ">>"), state}
|
||||
@@ -1278,7 +1343,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp bitstring_segment_to_algebra({{:<-, meta, [left, right]}, i}, state, last) do
|
||||
left = {:special, :bitstring_segment, [left, last]}
|
||||
left = {{:special, :bitstring_segment}, meta, [left, last]}
|
||||
{doc, state} = quoted_to_algebra({:<-, meta, [left, right]}, :parens_arg, state)
|
||||
{bitstring_wrap_parens(doc, i, last), state}
|
||||
end
|
||||
@@ -1286,8 +1351,17 @@ defmodule Code.Formatter do
|
||||
defp bitstring_segment_to_algebra({{:::, _, [segment, spec]}, i}, state, last) do
|
||||
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
|
||||
{spec, state} = bitstring_spec_to_algebra(spec, state)
|
||||
doc = concat(concat(doc, "::"), wrap_in_parens_if_inspected_atom(spec))
|
||||
{bitstring_wrap_parens(doc, i, last), state}
|
||||
|
||||
spec = wrap_in_parens_if_inspected_atom(spec)
|
||||
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
|
||||
|
||||
doc =
|
||||
doc
|
||||
|> bitstring_wrap_parens(i, -1)
|
||||
|> concat("::")
|
||||
|> concat(spec)
|
||||
|
||||
{doc, state}
|
||||
end
|
||||
|
||||
defp bitstring_segment_to_algebra({segment, i}, state, last) do
|
||||
@@ -1305,40 +1379,38 @@ defmodule Code.Formatter do
|
||||
quoted_to_algebra_with_parens_if_operator(spec, :parens_arg, state)
|
||||
end
|
||||
|
||||
defp bitstring_wrap_parens(doc, i, last) do
|
||||
if i == 0 or i == last do
|
||||
string = format_to_string(doc)
|
||||
defp bitstring_wrap_parens(doc, i, last) when i == 0 or i == last do
|
||||
string = format_to_string(doc)
|
||||
|
||||
if (i == 0 and String.starts_with?(string, "<<")) or
|
||||
(i == last and String.ends_with?(string, ">>")) do
|
||||
wrap_in_parens(doc)
|
||||
else
|
||||
doc
|
||||
end
|
||||
if (i == 0 and String.starts_with?(string, ["~", "<<"])) or
|
||||
(i == last and String.ends_with?(string, [">>"])) do
|
||||
wrap_in_parens(doc)
|
||||
else
|
||||
doc
|
||||
end
|
||||
end
|
||||
|
||||
defp bitstring_wrap_parens(doc, _, _), do: doc
|
||||
|
||||
## Literals
|
||||
|
||||
defp list_to_algebra(meta, args, state) do
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :glue
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :break
|
||||
fun = "ed_to_algebra(&1, :parens_arg, &2)
|
||||
|
||||
{args_doc, _join, state} =
|
||||
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
|
||||
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
|
||||
|
||||
{surround("[", args_doc, "]"), state}
|
||||
end
|
||||
|
||||
defp map_to_algebra(meta, name_doc, [{:|, _, [left, right]}], state) do
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :glue
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :break
|
||||
fun = "ed_to_algebra(&1, :parens_arg, &2)
|
||||
{left_doc, state} = fun.(left, state)
|
||||
|
||||
{right_doc, _join, state} =
|
||||
args_to_algebra_with_comments(right, meta, false, false, join, state, fun)
|
||||
args_to_algebra_with_comments(right, meta, false, :none, join, state, fun)
|
||||
|
||||
args_doc =
|
||||
left_doc
|
||||
@@ -1350,11 +1422,11 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp map_to_algebra(meta, name_doc, args, state) do
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :glue
|
||||
join = if Keyword.get(meta, :eol, false), do: :line, else: :break
|
||||
fun = "ed_to_algebra(&1, :parens_arg, &2)
|
||||
|
||||
{args_doc, _join, state} =
|
||||
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
|
||||
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
|
||||
|
||||
name_doc = "%" |> concat(name_doc) |> concat("{")
|
||||
{surround(name_doc, args_doc, "}"), state}
|
||||
@@ -1365,9 +1437,9 @@ defmodule Code.Formatter do
|
||||
fun = "ed_to_algebra(&1, :parens_arg, &2)
|
||||
|
||||
{args_doc, join, state} =
|
||||
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
|
||||
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
|
||||
|
||||
if join == :flex_glue do
|
||||
if join == :flex_break do
|
||||
{"{" |> concat(args_doc) |> nest(1) |> concat("}") |> group(), state}
|
||||
else
|
||||
{surround("{", args_doc, "}"), state}
|
||||
@@ -1475,7 +1547,7 @@ defmodule Code.Formatter do
|
||||
defp heredoc_line(["", _ | _]), do: nest(line(), :reset)
|
||||
defp heredoc_line(_), do: line()
|
||||
|
||||
defp args_to_algebra_with_comments(args, meta, skip_parens?, next_break_fits?, join, state, fun) do
|
||||
defp args_to_algebra_with_comments(args, meta, skip_parens?, last_arg_mode, join, state, fun) do
|
||||
min_line = line(meta)
|
||||
max_line = end_line(meta)
|
||||
|
||||
@@ -1483,10 +1555,11 @@ defmodule Code.Formatter do
|
||||
{doc, state} = fun.(arg, state)
|
||||
|
||||
doc =
|
||||
cond do
|
||||
args != [] -> concat(doc, ",")
|
||||
next_break_fits? -> next_break_fits(doc, :enabled)
|
||||
true -> doc
|
||||
case args do
|
||||
[_ | _] -> concat_to_last_group(doc, ",")
|
||||
[] when last_arg_mode == :force_comma -> concat_to_last_group(doc, ",")
|
||||
[] when last_arg_mode == :next_break_fits -> next_break_fits(doc, :enabled)
|
||||
[] when last_arg_mode == :none -> doc
|
||||
end
|
||||
|
||||
{doc, @empty, newlines, state}
|
||||
@@ -1514,11 +1587,11 @@ defmodule Code.Formatter do
|
||||
join == :line or comments? ->
|
||||
{args_docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), :line, state}
|
||||
|
||||
join == :glue ->
|
||||
{args_docs |> Enum.reduce(&glue(&2, &1)), :glue, state}
|
||||
join == :break ->
|
||||
{args_docs |> Enum.reduce(&glue(&2, &1)), :break, state}
|
||||
|
||||
join == :flex_glue ->
|
||||
{args_docs |> Enum.reduce(&flex_glue(&2, &1)), :flex_glue, state}
|
||||
join == :flex_break ->
|
||||
{args_docs |> Enum.reduce(&flex_glue(&2, &1)), :flex_break, state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1701,7 +1774,7 @@ defmodule Code.Formatter do
|
||||
fun = &clause_args_to_algebra/2
|
||||
|
||||
{args_docs, _join, state} =
|
||||
args_to_algebra_with_comments([args], meta, false, false, :glue, state, fun)
|
||||
args_to_algebra_with_comments([args], meta, false, :none, :break, state, fun)
|
||||
|
||||
{args_docs, state}
|
||||
end
|
||||
@@ -1709,7 +1782,7 @@ defmodule Code.Formatter do
|
||||
# fn a, b, c when d -> e end
|
||||
defp clause_args_to_algebra([{:when, meta, args}], state) do
|
||||
{args, right} = split_last(args)
|
||||
left = {:special, :clause_args, [args]}
|
||||
left = {{:special, :clause_args}, meta, [args]}
|
||||
binary_op_to_algebra(:when, "when", meta, left, right, :no_parens_arg, state)
|
||||
end
|
||||
|
||||
@@ -1839,6 +1912,9 @@ defmodule Code.Formatter do
|
||||
|
||||
## Quoted helpers
|
||||
|
||||
defp left_op_context(context), do: force_many_args_or_operand(context, :parens_arg)
|
||||
defp right_op_context(context), do: force_many_args_or_operand(context, :operand)
|
||||
|
||||
defp force_many_args_or_operand(:no_parens_one_arg, _choice), do: :no_parens_arg
|
||||
defp force_many_args_or_operand(:parens_one_arg, _choice), do: :parens_arg
|
||||
defp force_many_args_or_operand(:no_parens_arg, _choice), do: :no_parens_arg
|
||||
@@ -2000,38 +2076,47 @@ defmodule Code.Formatter do
|
||||
)
|
||||
end
|
||||
|
||||
# A literal list is a keyword or (... -> ...)
|
||||
defp last_arg_to_keyword([_ | _] = arg, _list_to_keyword?) do
|
||||
{keyword?(arg), arg}
|
||||
end
|
||||
|
||||
# This is a list of tuples, it can be converted to keywords.
|
||||
defp last_arg_to_keyword({:__block__, _, [[_ | _] = arg]} = block, true) do
|
||||
if keyword?(arg), do: {true, arg}, else: {false, block}
|
||||
end
|
||||
|
||||
# Otherwise we don't have a keyword.
|
||||
defp last_arg_to_keyword(arg, _list_to_keyword?) do
|
||||
{false, arg}
|
||||
end
|
||||
|
||||
defp force_keyword?(keyword) do
|
||||
match?([_, _ | _], keyword) and force_keyword?(keyword, MapSet.new())
|
||||
defp force_args?(args) do
|
||||
match?([_, _ | _], args) and force_args?(args, MapSet.new())
|
||||
end
|
||||
|
||||
defp force_keyword?([{{_, meta, _}, _} | keyword], lines) do
|
||||
line = line(meta)
|
||||
defp force_args?([[arg | _] | args], lines) do
|
||||
force_args?([arg | args], lines)
|
||||
end
|
||||
|
||||
if line in lines do
|
||||
defp force_args?([arg | args], lines) do
|
||||
line =
|
||||
case arg do
|
||||
{{_, meta, _}, _} -> line(meta)
|
||||
{_, meta, _} -> line(meta)
|
||||
end
|
||||
|
||||
if MapSet.member?(lines, line) do
|
||||
false
|
||||
else
|
||||
force_keyword?(keyword, MapSet.put(lines, line))
|
||||
force_args?(args, MapSet.put(lines, line))
|
||||
end
|
||||
end
|
||||
|
||||
defp force_keyword?([], _lines) do
|
||||
true
|
||||
end
|
||||
defp force_args?([], _lines), do: true
|
||||
|
||||
defp force_keyword(doc, arg) do
|
||||
if force_keyword?(arg), do: force_unfit(doc), else: doc
|
||||
if force_args?(arg), do: force_unfit(doc), else: doc
|
||||
end
|
||||
|
||||
defp keyword?([{key, _} | list]) do
|
||||
@@ -2064,6 +2149,20 @@ defmodule Code.Formatter do
|
||||
|
||||
## Algebra helpers
|
||||
|
||||
# Relying on the inner document is brittle and error prone.
|
||||
# It would be best if we had a mechanism to apply this.
|
||||
defp concat_to_last_group({:doc_cons, left, right}, concat) do
|
||||
{:doc_cons, left, concat_to_last_group(right, concat)}
|
||||
end
|
||||
|
||||
defp concat_to_last_group({:doc_group, group, mode}, concat) do
|
||||
{:doc_group, {:doc_cons, group, concat}, mode}
|
||||
end
|
||||
|
||||
defp concat_to_last_group(other, concat) do
|
||||
{:doc_cons, other, concat}
|
||||
end
|
||||
|
||||
defp ungroup_if_group({:doc_group, group, _mode}), do: group
|
||||
defp ungroup_if_group(other), do: other
|
||||
|
||||
|
||||
@@ -101,6 +101,9 @@ defimpl Collectable, for: BitString do
|
||||
acc, {:cont, x} when is_bitstring(x) ->
|
||||
<<IO.iodata_to_binary(acc)::bitstring, x::bitstring>>
|
||||
|
||||
acc, :done when is_bitstring(acc) ->
|
||||
acc
|
||||
|
||||
acc, :done ->
|
||||
IO.iodata_to_binary(acc)
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ defmodule DynamicSupervisor do
|
||||
DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
DynamicSupervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
@@ -84,6 +85,7 @@ defmodule DynamicSupervisor do
|
||||
Supervisor.start_child(__MODULE__, [foo, bar, baz])
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(initial_arg) do
|
||||
children = [
|
||||
# Or the deprecated: worker(MyWorker, [initial_arg])
|
||||
@@ -110,6 +112,7 @@ defmodule DynamicSupervisor do
|
||||
DynamicSupervisor.start_child(__MODULE__, spec)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(initial_arg) do
|
||||
DynamicSupervisor.init(
|
||||
strategy: :one_for_one,
|
||||
@@ -159,6 +162,13 @@ defmodule DynamicSupervisor do
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :one_for_one
|
||||
|
||||
@typedoc "Return values of `start_child` functions"
|
||||
@type on_start_child ::
|
||||
{:ok, pid}
|
||||
| {:ok, pid, info :: term}
|
||||
| :ignore
|
||||
| {:error, {:already_started, pid} | :max_children | term}
|
||||
|
||||
defstruct [
|
||||
:args,
|
||||
:extra_arguments,
|
||||
@@ -208,9 +218,6 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
@doc false
|
||||
def init(arg)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -288,7 +295,7 @@ defmodule DynamicSupervisor do
|
||||
this function returns `{:error, :max_children}`.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor(), :supervisor.child_spec() | {module, term} | module) ::
|
||||
Supervisor.on_start_child()
|
||||
on_start_child()
|
||||
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
|
||||
validate_and_start_child(supervisor, child_spec)
|
||||
end
|
||||
@@ -495,7 +502,14 @@ defmodule DynamicSupervisor do
|
||||
|
||||
case mod.init(args) do
|
||||
{:ok, flags} when is_map(flags) ->
|
||||
state = %DynamicSupervisor{mod: mod, args: args, name: name || {self(), mod}}
|
||||
name =
|
||||
cond do
|
||||
is_nil(name) -> {self(), mod}
|
||||
is_atom(name) -> {:local, name}
|
||||
is_tuple(name) -> name
|
||||
end
|
||||
|
||||
state = %DynamicSupervisor{mod: mod, args: args, name: name}
|
||||
|
||||
case init(state, flags) do
|
||||
{:ok, state} -> {:ok, state}
|
||||
|
||||
@@ -23,10 +23,17 @@ defmodule GenServer do
|
||||
|
||||
# Callbacks
|
||||
|
||||
@impl true
|
||||
def init(stack) do
|
||||
{:ok, stack}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:pop, _from, [h | t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
end
|
||||
@@ -151,22 +158,15 @@ defmodule GenServer do
|
||||
|
||||
# Server (callbacks)
|
||||
|
||||
@impl true
|
||||
def handle_call(:pop, _from, [h | t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, state) do
|
||||
# Call the default implementation from GenServer
|
||||
super(request, from, state)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item | state]}
|
||||
end
|
||||
|
||||
def handle_cast(request, state) do
|
||||
super(request, state)
|
||||
end
|
||||
end
|
||||
|
||||
In practice, it is common to have both server and client functions in
|
||||
@@ -196,11 +196,13 @@ defmodule GenServer do
|
||||
GenServer.start_link(__MODULE__, %{})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(state) do
|
||||
schedule_work() # Schedule work to be performed on start
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:work, state) do
|
||||
# Do the desired work here
|
||||
schedule_work() # Reschedule once more
|
||||
@@ -664,8 +666,8 @@ defmodule GenServer do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
But you want to define your own implementation that converts the \
|
||||
arguments given to GenServer.start_link/3 to the server state
|
||||
You can copy the implementation above or define your own that converts \
|
||||
the arguments given to GenServer.start_link/3 to the server state.
|
||||
"""
|
||||
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
|
||||
@@ -4571,7 +4571,7 @@ defmodule Kernel do
|
||||
allowed in guards, and otherwise creates a private macro that can be used
|
||||
both inside or outside guards in the current module.
|
||||
|
||||
Similar to `defmacrop/2`, `defguardp/2` must be defined before its use
|
||||
Similar to `defmacrop/2`, `defguardp/1` must be defined before its use
|
||||
in the current module.
|
||||
"""
|
||||
@spec defguardp(Macro.t()) :: Macro.t()
|
||||
|
||||
@@ -356,6 +356,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
{:file_done, child_pid, file, {kind, reason, stack}} ->
|
||||
discard_down(child_pid)
|
||||
print_error(file, kind, reason, stack)
|
||||
cancel_waiting_timer(queued, child_pid)
|
||||
terminate(queued)
|
||||
{:error, [to_error(file, kind, reason, stack)], warnings}
|
||||
|
||||
|
||||
@@ -200,8 +200,8 @@ defmodule Kernel.Utils do
|
||||
|
||||
defp extract_refs_from_args(args) do
|
||||
Macro.postwalk(args, [], fn
|
||||
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
{var, [{ref, context} | acc]}
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
{var, [{ref, var_context(meta, context)} | acc]}
|
||||
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
@@ -211,8 +211,8 @@ defmodule Kernel.Utils do
|
||||
# Finds every reference to `refs` in `guard` and wraps them in an unquote.
|
||||
defp unquote_every_ref(guard, refs) do
|
||||
Macro.postwalk(guard, fn
|
||||
{ref, _meta, context} = var when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, context} in refs do
|
||||
{ref, meta, context} = var when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, var_context(meta, context)} in refs do
|
||||
true -> literal_unquote(var)
|
||||
false -> var
|
||||
end
|
||||
@@ -226,9 +226,11 @@ defmodule Kernel.Utils do
|
||||
defp unquote_refs_once(guard, refs) do
|
||||
{_, used_refs} =
|
||||
Macro.postwalk(guard, [], fn
|
||||
{ref, _meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
case {ref, context} in refs and {ref, context} not in acc do
|
||||
true -> {var, [{ref, context} | acc]}
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
pair = {ref, var_context(meta, context)}
|
||||
|
||||
case pair in refs and pair not in acc do
|
||||
true -> {var, [pair | acc]}
|
||||
false -> {var, acc}
|
||||
end
|
||||
|
||||
@@ -236,7 +238,7 @@ defmodule Kernel.Utils do
|
||||
{node, acc}
|
||||
end)
|
||||
|
||||
vars = for {ref, context} <- :lists.reverse(used_refs), do: {ref, [], context}
|
||||
vars = for {ref, context} <- :lists.reverse(used_refs), do: context_to_var(ref, context)
|
||||
exprs = for var <- vars, do: literal_unquote(var)
|
||||
|
||||
quote do
|
||||
@@ -252,4 +254,14 @@ defmodule Kernel.Utils do
|
||||
defp literal_unquote(ast) do
|
||||
{:unquote, [], List.wrap(ast)}
|
||||
end
|
||||
|
||||
defp context_to_var(ref, ctx) when is_atom(ctx), do: {ref, [], ctx}
|
||||
defp context_to_var(ref, ctx) when is_integer(ctx), do: {ref, [counter: ctx], nil}
|
||||
|
||||
defp var_context(meta, kind) do
|
||||
case :lists.keyfind(:counter, 1, meta) do
|
||||
{:counter, counter} -> counter
|
||||
false -> kind
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+41
-8
@@ -719,7 +719,13 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, []))
|
||||
def to_string(other, fun) do
|
||||
fun.(other, inspect_no_limit(other))
|
||||
end
|
||||
|
||||
defp inspect_no_limit(value) do
|
||||
Kernel.inspect(value, limit: :infinity, printable_limit: :infinity)
|
||||
end
|
||||
|
||||
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
|
||||
result =
|
||||
@@ -773,7 +779,7 @@ defmodule Macro do
|
||||
"\#{" <> to_string(arg, fun) <> "}"
|
||||
|
||||
binary when is_binary(binary) ->
|
||||
binary = inspect(binary, [])
|
||||
binary = inspect_no_limit(binary)
|
||||
:binary.part(binary, 1, byte_size(binary) - 2)
|
||||
end)
|
||||
|
||||
@@ -781,7 +787,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom) do
|
||||
inspect(atom, [])
|
||||
inspect_no_limit(atom)
|
||||
end
|
||||
|
||||
defp module_to_string({:&, _, [val]} = expr, fun) when not is_integer(val) do
|
||||
@@ -839,11 +845,25 @@ defmodule Macro do
|
||||
:error
|
||||
end
|
||||
|
||||
defp sigil_call({sigil, _, [{:<<>>, _, _} = bin, args]} = ast, fun)
|
||||
defp sigil_call({sigil, _, [{:<<>>, _, _} = parts, args]} = ast, fun)
|
||||
when is_atom(sigil) and is_list(args) do
|
||||
case Atom.to_string(sigil) do
|
||||
<<"sigil_", name>> ->
|
||||
{:ok, fun.(ast, "~" <> <<name>> <> interpolate(bin, fun) <> sigil_args(args, fun))}
|
||||
<<"sigil_", name>> when name >= ?A and name <= ?Z ->
|
||||
{:<<>>, _, [binary]} = parts
|
||||
|
||||
formatted =
|
||||
if :binary.last(binary) == ?\n do
|
||||
binary = String.replace(binary, ~s["""], ~s["\\""])
|
||||
<<?~, name, ~s["""\n], binary::binary, ~s["""], sigil_args(args, fun)::binary>>
|
||||
else
|
||||
{left, right} = select_sigil_container(binary)
|
||||
<<?~, name, left, binary::binary, right, sigil_args(args, fun)::binary>>
|
||||
end
|
||||
|
||||
{:ok, fun.(ast, formatted)}
|
||||
|
||||
<<"sigil_", name>> when name >= ?a and name <= ?z ->
|
||||
{:ok, fun.(ast, "~" <> <<name>> <> interpolate(parts, fun) <> sigil_args(args, fun))}
|
||||
|
||||
_ ->
|
||||
:error
|
||||
@@ -854,6 +874,18 @@ defmodule Macro do
|
||||
:error
|
||||
end
|
||||
|
||||
defp select_sigil_container(binary) do
|
||||
cond do
|
||||
:binary.match(binary, ["\""]) == :nomatch -> {?", ?"}
|
||||
:binary.match(binary, ["\'"]) == :nomatch -> {?', ?'}
|
||||
:binary.match(binary, ["(", ")"]) == :nomatch -> {?(, ?)}
|
||||
:binary.match(binary, ["[", "]"]) == :nomatch -> {?[, ?]}
|
||||
:binary.match(binary, ["{", "}"]) == :nomatch -> {?{, ?}}
|
||||
:binary.match(binary, ["<", ">"]) == :nomatch -> {?<, ?>}
|
||||
true -> {?/, ?/}
|
||||
end
|
||||
end
|
||||
|
||||
defp sigil_args([], _fun), do: ""
|
||||
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
|
||||
|
||||
@@ -952,8 +984,9 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp map_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
Enum.map_join(list, ", ", fn
|
||||
{key, value} -> to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
other -> to_string(other, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -505,6 +505,8 @@ defmodule Map do
|
||||
side into the struct, even if the key is not part of the struct. Instead,
|
||||
use `Kernel.struct/2`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
|
||||
|
||||
@@ -236,8 +236,9 @@ defmodule Port do
|
||||
|
||||
def info(port, :registered_name) do
|
||||
case :erlang.port_info(port, :registered_name) do
|
||||
:undefined -> nil
|
||||
[] -> {:registered_name, []}
|
||||
other -> nillify(other)
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -28,14 +28,17 @@ defmodule Supervisor do
|
||||
|
||||
## Callbacks
|
||||
|
||||
@impl true
|
||||
def init(stack) do
|
||||
{:ok, stack}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:pop, _from, [h | t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:push, h}, t) do
|
||||
{:noreply, [h | t]}
|
||||
end
|
||||
@@ -121,8 +124,8 @@ defmodule Supervisor do
|
||||
then awaiting for a time interval for the child process to terminate. This
|
||||
interval defaults to 5000 milliseconds. If the child process does not
|
||||
terminate in this interval, the supervisor abruptly terminates the child
|
||||
with reason `:brutal_kill`. The shutdown time can be configured in the
|
||||
child specification which is fully detailed in the next section.
|
||||
with reason `:kill`. The shutdown time can be configured in the child
|
||||
specification which is fully detailed in the next section.
|
||||
|
||||
If the child process is not trapping exits, it will shutdown immediately
|
||||
when it receives the first exit signal. If the child process is trapping
|
||||
@@ -209,7 +212,8 @@ defmodule Supervisor do
|
||||
* `:permanent` - the child process is always restarted.
|
||||
|
||||
* `:temporary` - the child process is never restarted, regardless
|
||||
of the supervision strategy.
|
||||
of the supervision strategy: any termination (even abnormal) is
|
||||
considered successful.
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e., with an exit reason other than
|
||||
@@ -342,6 +346,7 @@ defmodule Supervisor do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
children = [
|
||||
{Stack, [:hello]}
|
||||
@@ -440,6 +445,9 @@ defmodule Supervisor do
|
||||
the child processes, i.e., the child processes after the terminated
|
||||
one in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted.
|
||||
|
||||
In the above, process termination refers to unsuccessful termination, which
|
||||
is determined by the `:restart` option.
|
||||
|
||||
There is also a deprecated strategy called `:simple_one_for_one` which
|
||||
has been replaced by the `DynamicSupervisor`. The `:simple_one_for_one`
|
||||
@@ -476,9 +484,6 @@ defmodule Supervisor do
|
||||
end
|
||||
|
||||
defoverridable child_spec: 1
|
||||
|
||||
@doc false
|
||||
def init(arg)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -439,8 +439,8 @@ defmodule Task do
|
||||
defp get_info(pid) do
|
||||
self_or_name =
|
||||
case Process.info(pid, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
{:registered_name, name} when is_atom(name) -> name
|
||||
_ -> pid
|
||||
end
|
||||
|
||||
{node(), self_or_name}
|
||||
@@ -648,7 +648,11 @@ defmodule Task do
|
||||
@spec yield_many([t], timeout) :: [{t, {:ok, term} | {:exit, term} | nil}]
|
||||
def yield_many(tasks, timeout \\ 5000) do
|
||||
timeout_ref = make_ref()
|
||||
timer_ref = Process.send_after(self(), timeout_ref, timeout)
|
||||
|
||||
timer_ref =
|
||||
if timeout != :infinity do
|
||||
Process.send_after(self(), timeout_ref, timeout)
|
||||
end
|
||||
|
||||
try do
|
||||
yield_many(tasks, timeout_ref, :infinity)
|
||||
@@ -656,7 +660,7 @@ defmodule Task do
|
||||
{:noconnection, reason} ->
|
||||
exit({reason, {__MODULE__, :yield_many, [tasks, timeout]}})
|
||||
after
|
||||
Process.cancel_timer(timer_ref)
|
||||
timer_ref && Process.cancel_timer(timer_ref)
|
||||
receive do: (^timeout_ref -> :ok), after: (0 -> :ok)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -348,8 +348,8 @@ defmodule Task.Supervisor do
|
||||
defp get_info(self) do
|
||||
name =
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self
|
||||
{:registered_name, name} -> name
|
||||
{:registered_name, name} when is_atom(name) -> name
|
||||
_ -> self
|
||||
end
|
||||
|
||||
{node(), name}
|
||||
|
||||
@@ -422,11 +422,13 @@ defmodule URI do
|
||||
|
||||
parts = Regex.run(regex, string)
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
destructure [_, _, scheme, _, authority, path, query_with_question_mark, _, _, fragment],
|
||||
parts
|
||||
|
||||
scheme = nillify(scheme)
|
||||
authority = nillify(authority)
|
||||
path = nillify(path)
|
||||
query = nillify(query)
|
||||
query = nillify_query(query_with_question_mark)
|
||||
{userinfo, host, port} = split_authority(authority)
|
||||
|
||||
scheme = scheme && String.downcase(scheme)
|
||||
@@ -444,6 +446,10 @@ defmodule URI do
|
||||
}
|
||||
end
|
||||
|
||||
defp nillify_query("?"), do: ""
|
||||
defp nillify_query("?" <> query), do: query
|
||||
defp nillify_query(_other), do: nil
|
||||
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(string) do
|
||||
regex = Regex.recompile!(~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/)
|
||||
|
||||
@@ -12,7 +12,7 @@ Elixir version | Supported Erlang/OTP versions
|
||||
1.3 | 18 - 19
|
||||
1.4 | 18 - 19 (and OTP 20 from v1.4.5)
|
||||
1.5 | 18 - 20
|
||||
1.6 | 19 - 20
|
||||
1.6 | 19 - 20 (and OTP 21 from v1.6.5)
|
||||
|
||||
While Elixir often adds compatibility to new Erlang versions on released branches, such as support for OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang release.
|
||||
|
||||
@@ -64,6 +64,7 @@ EEx: `<%=` in middle and end expressions | [v1.5] | Use `<%` (= i
|
||||
`Float.to_string/2` | [v1.4] | `:erlang.float_to_binary/2` (OTP 17)
|
||||
`HashDict` module | [v1.4] | `Map` (v1.2)
|
||||
`HashSet` module | [v1.4] | `MapSet` (v1.1)
|
||||
Multi-letter aliases in `OptionParser` | [v1.4] | Use single-letter aliases (v1.0)
|
||||
`Set` module | [v1.4] | `MapSet` (v1.1)
|
||||
`Stream.uniq/2` | [v1.4] | `Stream.uniq_by/2` (v1.2)
|
||||
`IEx.Helpers.import_file/2` | [v1.4] | `IEx.Helpers.import_file_if_available/1` (v1.3)
|
||||
@@ -71,6 +72,7 @@ EEx: `<%=` in middle and end expressions | [v1.5] | Use `<%` (= i
|
||||
`Mix.Utils.underscore/1` | [v1.4] | `Macro.underscore/1` (v1.2)
|
||||
Variable used as function call | [v1.4] | Use parentheses (v1.0)
|
||||
Anonymous functions with no expression after `->` | [v1.4] | Use an expression or explicitly return `nil` (v1.0)
|
||||
Support for making private functions overridable | [v1.4] | Use public functions (v1.0)
|
||||
`Dict` module | [v1.3] | `Keyword` (v1.0) or `Map` (v1.2)
|
||||
`Keyword.size/1` | [v1.3] | `Kernel.length/1` (v1.0)
|
||||
`Map.size/1` | [v1.3] | `Kernel.map_size/1` (v1.0)
|
||||
|
||||
@@ -26,7 +26,7 @@ Operator
|
||||
`\|` | Right to left
|
||||
`::` | Right to left
|
||||
`when` | Right to left
|
||||
`<-`, `\\` | Left to right
|
||||
`<-` `\\` | Left to right
|
||||
|
||||
## Comparison operators
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.6.2"},
|
||||
{vsn, "1.6.5"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
@@ -293,29 +293,43 @@ add_info_function(Line, Module, Def, Defmacro, Deprecated) ->
|
||||
AllowedArgs = lists:map(fun(Atom) -> {atom, Line, Atom} end, AllowedAttrs),
|
||||
|
||||
Spec =
|
||||
{attribute, Line, spec, {{'__info__', 1},
|
||||
[{type, Line, 'fun', [
|
||||
{type, Line, product, [
|
||||
{type, Line, union, AllowedArgs}
|
||||
]},
|
||||
{type, Line, union, [
|
||||
{type, Line, atom, []},
|
||||
{type, Line, list, [
|
||||
%% TODO: Remove this check once we depend only on 20
|
||||
case erlang:system_info(otp_release) of
|
||||
"19" ->
|
||||
{attribute, Line, spec, {{'__info__', 1},
|
||||
[{type, Line, 'fun', [
|
||||
{type, Line, product, [
|
||||
{type, Line, union, AllowedArgs}
|
||||
]},
|
||||
{type, Line, union, [
|
||||
{type, Line, tuple, [
|
||||
{type, Line, atom, []},
|
||||
{type, Line, any, []}
|
||||
]},
|
||||
{type, Line, tuple, [
|
||||
{type, Line, atom, []},
|
||||
{type, Line, byte, []},
|
||||
{type, Line, integer, []}
|
||||
{type, Line, atom, []},
|
||||
{type, Line, list, [
|
||||
{type, Line, union, [
|
||||
{type, Line, tuple, [
|
||||
{type, Line, atom, []},
|
||||
{type, Line, any, []}
|
||||
]},
|
||||
{type, Line, tuple, [
|
||||
{type, Line, atom, []},
|
||||
{type, Line, byte, []},
|
||||
{type, Line, integer, []}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}]
|
||||
}},
|
||||
]}]
|
||||
}};
|
||||
|
||||
_ ->
|
||||
{attribute, Line, spec, {{'__info__', 1},
|
||||
[{type, Line, 'fun', [
|
||||
{type, Line, product, [
|
||||
{type, Line, union, AllowedArgs}
|
||||
]},
|
||||
{type, Line, any, []}
|
||||
]}]
|
||||
}}
|
||||
end,
|
||||
|
||||
Info =
|
||||
{function, 0, '__info__', 1, [
|
||||
|
||||
@@ -63,6 +63,7 @@ handle_file_warning(_, _File, {_Line, erl_lint, {undefined_behaviour_func, _, _}
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {undefined_behaviour, _}}) -> ok;
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {ill_defined_behaviour_callbacks, _}}) -> ok;
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {ill_defined_optional_callbacks, _}}) -> ok;
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {deprecated,{erlang,get_stacktrace,0},_}}) -> ok;
|
||||
|
||||
handle_file_warning(_, File, {Line, Module, Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
@@ -86,6 +87,10 @@ format_error(erl_lint, {undefined_function, {F, A}}) ->
|
||||
format_error(erl_lint, {spec_fun_undefined, {M, F, A}}) ->
|
||||
io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]);
|
||||
|
||||
%% Mention the capture operator in make_fun
|
||||
format_error(sys_core_fold, {no_effect, {erlang, make_fun, 3}}) ->
|
||||
"the result of the capture operator & (:erlang.make_fun/3) is never used";
|
||||
|
||||
%% Make no_effect clauses pretty
|
||||
format_error(sys_core_fold, {no_effect, {erlang, F, A}}) ->
|
||||
{Fmt, Args} = case erl_internal:comp_op(F, A) of
|
||||
|
||||
@@ -21,7 +21,7 @@ translate(Meta, Args, Return, S) ->
|
||||
|
||||
case comprehension_expr(TInto, TExpr) of
|
||||
{inline, TIntoExpr} ->
|
||||
{build_inline(Ann, TCases, TIntoExpr, TInto, TUniq, SF), SF};
|
||||
build_inline(Ann, TCases, TIntoExpr, TInto, TUniq, SF);
|
||||
{into, TIntoExpr} ->
|
||||
build_into(Ann, TCases, TIntoExpr, TInto, TUniq, SF)
|
||||
end.
|
||||
@@ -91,28 +91,29 @@ collect_filters([], Acc) ->
|
||||
|
||||
build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
case not Uniq and lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
|
||||
true -> build_comprehension(Ann, Clauses, Expr, Into);
|
||||
true -> {build_comprehension(Ann, Clauses, Expr, Into), S};
|
||||
false -> build_inline_each(Ann, Clauses, Expr, Into, Uniq, S)
|
||||
end.
|
||||
|
||||
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
|
||||
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
|
||||
{build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S), S};
|
||||
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
|
||||
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
|
||||
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
|
||||
elixir_erl:remote(Ann, lists, reverse, [ReduceExpr]);
|
||||
{elixir_erl:remote(Ann, lists, reverse, [ReduceExpr]), S};
|
||||
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
{InnerValue, SV} = build_var(Ann, S),
|
||||
Generated = erl_anno:set_generated(true, Ann),
|
||||
|
||||
InnerFun = fun(InnerExpr, InnerAcc) ->
|
||||
{'case', Ann, InnerExpr, [
|
||||
{clause, Ann,
|
||||
{clause, Generated,
|
||||
[InnerValue],
|
||||
[[elixir_erl:remote(Ann, erlang, is_binary, [InnerValue]),
|
||||
elixir_erl:remote(Ann, erlang, is_list, [InnerAcc])]],
|
||||
[{cons, Ann, InnerAcc, InnerValue}]},
|
||||
{clause, Ann,
|
||||
[{cons, Generated, InnerAcc, InnerValue}]},
|
||||
{clause, Generated,
|
||||
[InnerValue],
|
||||
[[elixir_erl:remote(Ann, erlang, is_bitstring, [InnerValue]),
|
||||
elixir_erl:remote(Ann, erlang, is_bitstring, [InnerAcc])]],
|
||||
@@ -120,14 +121,14 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
{bin_element, Ann, InnerAcc, default, [bitstring]},
|
||||
{bin_element, Ann, InnerValue, default, [bitstring]}
|
||||
]}]},
|
||||
{clause, Ann,
|
||||
{clause, Generated,
|
||||
[InnerValue],
|
||||
[[elixir_erl:remote(Ann, erlang, is_bitstring, [InnerValue])]],
|
||||
[{bin, Ann, [
|
||||
{bin_element, Ann, elixir_erl:remote(Ann, erlang, iolist_to_binary, [InnerAcc]), default, [bitstring]},
|
||||
{bin_element, Ann, InnerValue, default, [bitstring]}
|
||||
]}]},
|
||||
{clause, Ann,
|
||||
{clause, Generated,
|
||||
[InnerValue],
|
||||
[],
|
||||
[elixir_erl:remote(Ann, erlang, error, [{tuple, Ann, [{atom, Ann, badarg}, InnerValue]}])]}
|
||||
@@ -135,11 +136,21 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
end,
|
||||
|
||||
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
|
||||
elixir_erl:remote(Ann, erlang, iolist_to_binary, [ReduceExpr]).
|
||||
|
||||
{{'case', Ann, ReduceExpr, [
|
||||
{clause, Ann,
|
||||
[InnerValue],
|
||||
[[elixir_erl:remote(Ann, erlang, is_bitstring, [InnerValue])]],
|
||||
[InnerValue]},
|
||||
{clause, Ann,
|
||||
[InnerValue],
|
||||
[],
|
||||
[elixir_erl:remote(Ann, erlang, iolist_to_binary, [InnerValue])]}
|
||||
]}, SV}.
|
||||
|
||||
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
|
||||
ReduceExpr = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
|
||||
{elixir_erl:remote(Ann, maps, from_list, [ReduceExpr]), S};
|
||||
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
|
||||
{elixir_erl:remote(Ann, maps, from_list, [ReduceExpr]), SR};
|
||||
build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
{Fun, SF} = build_var(Ann, S),
|
||||
{Acc, SA} = build_var(Ann, SF),
|
||||
|
||||
@@ -182,7 +182,8 @@ tokenize([$~, S, H, H, H | T] = Original, Line, Column, Scope, Tokens) when ?is_
|
||||
{ok, NewLine, NewColumn, Parts, Rest} ->
|
||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
||||
Token = {sigil, {Line, Column, nil}, S, Parts, Modifiers, <<H, H, H>>},
|
||||
tokenize(Final, NewLine, NewColumn, Scope, [Token | Tokens]);
|
||||
NewColumn2 = NewColumn + length(Modifiers),
|
||||
tokenize(Final, NewLine, NewColumn2, Scope, [Token | Tokens]);
|
||||
{error, Reason} ->
|
||||
{error, Reason, Original, Tokens}
|
||||
end;
|
||||
@@ -192,7 +193,8 @@ tokenize([$~, S, H | T] = Original, Line, Column, Scope, Tokens) when ?is_sigil(
|
||||
{NewLine, NewColumn, Parts, Rest} ->
|
||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
||||
Token = {sigil, {Line, Column, nil}, S, Parts, Modifiers, <<H>>},
|
||||
tokenize(Final, NewLine, NewColumn, Scope, [Token | Tokens]);
|
||||
NewColumn2 = NewColumn + length(Modifiers),
|
||||
tokenize(Final, NewLine, NewColumn2, Scope, [Token | Tokens]);
|
||||
{error, Reason} ->
|
||||
Sigil = [$~, S, H],
|
||||
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
@@ -735,7 +737,7 @@ extract_heredoc(Line0, Column0, Rest0, Marker, Scope) ->
|
||||
%% in the final heredoc body three lines below.
|
||||
case extract_heredoc_body(Line0, Column0, Marker, [$\n | Rest1], []) of
|
||||
{ok, Line1, Body, Rest2, Spaces} ->
|
||||
{ok, Line1, 1, tl(remove_heredoc_spaces(Body, Spaces, Marker, Scope)), Rest2};
|
||||
{ok, Line1, 4 + Spaces, tl(remove_heredoc_spaces(Body, Spaces, Marker, Scope)), Rest2};
|
||||
{error, Reason, ErrorLine} ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
{Message, Token} = heredoc_error_message(Reason, Line0, Terminator),
|
||||
|
||||
@@ -201,9 +201,23 @@ defmodule Code.Formatter.CallsTest do
|
||||
assert_format bad, good, @short_length
|
||||
end
|
||||
|
||||
test "with arguments on comma limit" do
|
||||
bad = """
|
||||
import(foo(abc, cde), :next)
|
||||
"""
|
||||
|
||||
good = """
|
||||
import(
|
||||
foo(abc, cde),
|
||||
:next
|
||||
)
|
||||
"""
|
||||
|
||||
assert_format bad, good, @medium_length
|
||||
end
|
||||
|
||||
test "with keyword lists" do
|
||||
assert_same "foo(foo: 1, bar: 2)"
|
||||
|
||||
assert_same "foo(:hello, foo: 1, bar: 2)"
|
||||
|
||||
bad = """
|
||||
@@ -220,8 +234,14 @@ defmodule Code.Formatter.CallsTest do
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
|
||||
# Check it preserves multiline.
|
||||
assert_same good
|
||||
bad = """
|
||||
foo(:hello, foo: 1,
|
||||
bar: 2)
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
foo(:hello, foo: 1, bar: 2)
|
||||
"""
|
||||
end
|
||||
|
||||
test "with lists maybe rewritten as keyword lists" do
|
||||
@@ -235,21 +255,19 @@ defmodule Code.Formatter.CallsTest do
|
||||
assert_same "bar = if foo, do: bar, else: baz"
|
||||
|
||||
assert_same """
|
||||
for :one,
|
||||
:two,
|
||||
:three,
|
||||
fn ->
|
||||
:ok
|
||||
end
|
||||
""",
|
||||
@short_length
|
||||
for :one,
|
||||
:two,
|
||||
:three,
|
||||
fn ->
|
||||
:ok
|
||||
end
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
for :one, fn ->
|
||||
:ok
|
||||
end
|
||||
""",
|
||||
@medium_length
|
||||
for :one, fn ->
|
||||
:ok
|
||||
end
|
||||
"""
|
||||
end
|
||||
|
||||
test "without parens on line limit" do
|
||||
@@ -263,17 +281,78 @@ defmodule Code.Formatter.CallsTest do
|
||||
assert_format bad, good, @short_length
|
||||
end
|
||||
|
||||
test "without parens on comma limit" do
|
||||
bad = """
|
||||
import foo(abc, cde), :next
|
||||
"""
|
||||
|
||||
good = """
|
||||
import foo(
|
||||
abc,
|
||||
cde
|
||||
),
|
||||
:next
|
||||
"""
|
||||
|
||||
assert_format bad, good, @medium_length
|
||||
end
|
||||
|
||||
test "without parens and with keyword lists preserves multiline" do
|
||||
assert_same """
|
||||
defstruct foo: 1,
|
||||
bar: 2
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
config :app,
|
||||
foo: 1
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
config :app,
|
||||
foo: 1,
|
||||
bar: 2
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
config :app, :key,
|
||||
foo: 1,
|
||||
bar: 2
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
config :app,
|
||||
:key,
|
||||
foo: 1,
|
||||
bar: 2
|
||||
"""
|
||||
|
||||
bad = """
|
||||
config :app, foo: 1,
|
||||
bar: 2
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
config :app,
|
||||
foo: 1,
|
||||
bar: 2
|
||||
"""
|
||||
end
|
||||
|
||||
test "without parens and with keyword lists on comma limit" do
|
||||
bad = """
|
||||
import foo(abc, cde), opts: :next
|
||||
"""
|
||||
|
||||
good = """
|
||||
import foo(
|
||||
abc,
|
||||
cde
|
||||
),
|
||||
opts: :next
|
||||
"""
|
||||
|
||||
assert_format bad, good, @medium_length
|
||||
end
|
||||
|
||||
test "without parens and with keyword lists on line limit" do
|
||||
@@ -640,6 +719,14 @@ defmodule Code.Formatter.CallsTest do
|
||||
|
||||
# Doesn't preserve this because only the beginning has a newline
|
||||
assert_format "Remote.call(\nfoo, bar, baz)", "Remote.call(foo, bar, baz)"
|
||||
|
||||
assert_same """
|
||||
Remote.call(
|
||||
:hello,
|
||||
:foo,
|
||||
fn -> :bar end
|
||||
)
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -56,36 +56,19 @@ defmodule Code.Formatter.CommentsTest do
|
||||
|
||||
assert_format bad, good
|
||||
|
||||
bad = """
|
||||
foo # this is foo
|
||||
|> bar # this is bar
|
||||
|> baz # this is baz
|
||||
"""
|
||||
|
||||
good = """
|
||||
# this is foo
|
||||
# this is bar
|
||||
# this is baz
|
||||
foo
|
||||
|> bar
|
||||
|> baz
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
|
||||
bad = """
|
||||
foo # this is foo
|
||||
| bar # this is bar
|
||||
| baz # this is baz
|
||||
++ bar # this is bar
|
||||
++ baz # this is baz
|
||||
"""
|
||||
|
||||
good = """
|
||||
# this is foo
|
||||
# this is bar
|
||||
# this is baz
|
||||
foo
|
||||
| bar
|
||||
| baz
|
||||
foo ++
|
||||
bar ++
|
||||
baz
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
@@ -721,6 +704,118 @@ defmodule Code.Formatter.CommentsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "operators" do
|
||||
test "with comment before, during and after uniform pipelines" do
|
||||
assert_same """
|
||||
foo
|
||||
# |> bar
|
||||
# |> baz
|
||||
|> bat
|
||||
"""
|
||||
|
||||
bad = """
|
||||
# before
|
||||
foo # this is foo
|
||||
|> bar # this is bar
|
||||
|> baz # this is baz
|
||||
# after
|
||||
"""
|
||||
|
||||
good = """
|
||||
# before
|
||||
# this is foo
|
||||
foo
|
||||
# this is bar
|
||||
|> bar
|
||||
# this is baz
|
||||
|> baz
|
||||
|
||||
# after
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
end
|
||||
|
||||
test "with comment before, during and after mixed pipelines" do
|
||||
assert_same """
|
||||
foo
|
||||
# |> bar
|
||||
# |> baz
|
||||
~> bat
|
||||
"""
|
||||
|
||||
bad = """
|
||||
# before
|
||||
foo # this is foo
|
||||
~> bar # this is bar
|
||||
<|> baz # this is baz
|
||||
# after
|
||||
"""
|
||||
|
||||
good = """
|
||||
# before
|
||||
# this is foo
|
||||
foo
|
||||
# this is bar
|
||||
~> bar
|
||||
# this is baz
|
||||
<|> baz
|
||||
|
||||
# after
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
end
|
||||
|
||||
test "with comment before, during and after uniform right" do
|
||||
assert_same """
|
||||
foo
|
||||
# | bar
|
||||
# | baz
|
||||
| bat
|
||||
"""
|
||||
|
||||
bad = """
|
||||
# before
|
||||
foo # this is foo
|
||||
| bar # this is bar
|
||||
| baz # this is baz
|
||||
# after
|
||||
"""
|
||||
|
||||
good = """
|
||||
# before
|
||||
# this is foo
|
||||
foo
|
||||
# this is bar
|
||||
| bar
|
||||
# this is baz
|
||||
| baz
|
||||
|
||||
# after
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
end
|
||||
|
||||
test "with comment before, during and after mixed right" do
|
||||
assert_same """
|
||||
one
|
||||
# when two
|
||||
# when three
|
||||
when four
|
||||
# | five
|
||||
| six
|
||||
"""
|
||||
end
|
||||
|
||||
test "handles nodes without meta info" do
|
||||
assert_same "(a -> b) |> (c -> d)"
|
||||
assert_same "(a -> b) when c: d"
|
||||
assert_same "(a -> b) when (c -> d)"
|
||||
end
|
||||
end
|
||||
|
||||
describe "containers" do
|
||||
test "with comment outside before, during and after" do
|
||||
assert_same ~S"""
|
||||
|
||||
@@ -178,9 +178,30 @@ defmodule Code.Formatter.ContainersTest do
|
||||
assert_same keyword, @short_length
|
||||
end
|
||||
|
||||
test "with keyword lists on comma line limit" do
|
||||
bad = """
|
||||
[
|
||||
foooo: 1,
|
||||
barrr: 2
|
||||
]
|
||||
"""
|
||||
|
||||
good = """
|
||||
[
|
||||
foooo:
|
||||
1,
|
||||
barrr: 2
|
||||
]
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
end
|
||||
|
||||
test "with quoted keyword lists" do
|
||||
assert_same ~S(["with spaces": 1])
|
||||
assert_same ~S(["one #{two} three": 1])
|
||||
assert_same ~S(["\w": 1, "\\w": 2])
|
||||
assert_same ~S(["Elixir.Foo": 1, "Elixir.Bar": 2])
|
||||
assert_format ~S(["Foo": 1, "Bar": 2]), ~S([Foo: 1, Bar: 2])
|
||||
end
|
||||
|
||||
@@ -226,13 +247,19 @@ defmodule Code.Formatter.ContainersTest do
|
||||
assert_format "<<1,2,3>>", "<<1, 2, 3>>"
|
||||
end
|
||||
|
||||
test "add parens on first and last in case of ambiguity" do
|
||||
test "add parens on first and last in case of binary ambiguity" do
|
||||
assert_format "<< <<>> >>", "<<(<<>>)>>"
|
||||
assert_format "<< <<>> + <<>> >>", "<<(<<>> + <<>>)>>"
|
||||
assert_format "<< 1 + <<>> >>", "<<(1 + <<>>)>>"
|
||||
assert_format "<< <<>> + 1 >>", "<<(<<>> + 1)>>"
|
||||
assert_format "<< <<>>, <<>>, <<>> >>", "<<(<<>>), <<>>, (<<>>)>>"
|
||||
assert_format "<< <<>>::1, <<>>::2, <<>>::3 >>", "<<(<<>>::1), <<>>::2, <<>>::3>>"
|
||||
assert_format "<< <<>>::1, <<>>::2, <<>>::3 >>", "<<(<<>>)::1, <<>>::2, <<>>::3>>"
|
||||
assert_format "<< <<>>::<<>> >>", "<<(<<>>)::(<<>>)>>"
|
||||
end
|
||||
|
||||
test "add parens on first in case of operator ambiguity" do
|
||||
assert_format "<< ~~~1::8 >>", "<<(~~~1)::8>>"
|
||||
assert_format "<< ~s[foo]::binary >>", "<<(~s[foo])::binary>>"
|
||||
end
|
||||
|
||||
test "with modifiers" do
|
||||
@@ -455,7 +482,7 @@ defmodule Code.Formatter.ContainersTest do
|
||||
}
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
assert_format bad, good, line_length: 11
|
||||
end
|
||||
|
||||
test "removes trailing comma" do
|
||||
@@ -559,7 +586,7 @@ defmodule Code.Formatter.ContainersTest do
|
||||
}
|
||||
"""
|
||||
|
||||
assert_format bad, good, @short_length
|
||||
assert_format bad, good, line_length: 11
|
||||
end
|
||||
|
||||
test "removes trailing comma" do
|
||||
|
||||
@@ -348,17 +348,17 @@ defmodule Code.Formatter.IntegrationTest do
|
||||
test "no parens keywords at the end of the line" do
|
||||
bad = """
|
||||
defmodule Mod do
|
||||
defp token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char), do: token_list_downcase(rest, acc)
|
||||
defp token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33), do: token_list_downcase(rest, acc)
|
||||
def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char), do: token_list_downcase(rest, acc)
|
||||
def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33), do: token_list_downcase(rest, acc)
|
||||
end
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
defmodule Mod do
|
||||
defp token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
|
||||
def token_list_downcase(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
|
||||
do: token_list_downcase(rest, acc)
|
||||
|
||||
defp token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33),
|
||||
def token_list_downcase(some_really_long_arg11, some_really_long_arg22, some_really_long_arg33),
|
||||
do: token_list_downcase(rest, acc)
|
||||
end
|
||||
"""
|
||||
@@ -436,8 +436,8 @@ defmodule Code.Formatter.IntegrationTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "first argument in a call without parens" do
|
||||
bad = """
|
||||
test "first argument in a call without parens with comments" do
|
||||
assert_same """
|
||||
with bar ::
|
||||
:ok
|
||||
| :invalid
|
||||
@@ -445,28 +445,58 @@ defmodule Code.Formatter.IntegrationTest do
|
||||
| :other
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
# | :unknown
|
||||
with bar ::
|
||||
:ok
|
||||
| :invalid
|
||||
| :other
|
||||
"""
|
||||
|
||||
bad = """
|
||||
assert_same """
|
||||
@spec bar ::
|
||||
:ok
|
||||
| :invalid
|
||||
# | :unknown
|
||||
| :other
|
||||
"""
|
||||
end
|
||||
|
||||
test "capture with operators" do
|
||||
assert_same """
|
||||
"this works" |> (&String.upcase/1) |> (&String.downcase/1)
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
"this works" || (&String.upcase/1) || (&String.downcase/1)
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
"this works" == (&String.upcase/1) == (&String.downcase/1)
|
||||
"""
|
||||
|
||||
bad = """
|
||||
"this works" = (&String.upcase/1) = (&String.downcase/1)
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
# | :unknown
|
||||
@spec bar ::
|
||||
:ok
|
||||
| :invalid
|
||||
| :other
|
||||
"this works" = (&String.upcase/1) = &String.downcase/1
|
||||
"""
|
||||
|
||||
bad = """
|
||||
"this works" ++ (&String.upcase/1) ++ (&String.downcase/1)
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
"this works" ++ (&String.upcase/1) ++ &String.downcase/1
|
||||
"""
|
||||
|
||||
bad = """
|
||||
"this works" | (&String.upcase/1) | (&String.downcase/1)
|
||||
"""
|
||||
|
||||
assert_format bad, """
|
||||
"this works" | (&String.upcase/1) | &String.downcase/1
|
||||
"""
|
||||
|
||||
bad = ~S"""
|
||||
"this works" \\ (&String.upcase/1) \\ (&String.downcase/1)
|
||||
"""
|
||||
|
||||
assert_format bad, ~S"""
|
||||
"this works" \\ &String.upcase/1 \\ &String.downcase/1
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -812,9 +812,9 @@ defmodule Code.Formatter.OperatorsTest do
|
||||
end
|
||||
|
||||
test "with operators outside" do
|
||||
assert_same "(& &1) == & &2"
|
||||
assert_same "(& &1) and & &2"
|
||||
assert_same "(&foo/1) and &bar/1"
|
||||
assert_same "(& &1) == (& &2)"
|
||||
assert_same "(& &1) and (& &2)"
|
||||
assert_same "(&foo/1) and (&bar/1)"
|
||||
assert_same "[(&IO.puts/1) | &IO.puts/2]"
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
defmodule Dialyzer.ForBitstring do
|
||||
def foo() do
|
||||
for a <- 1..3, into: "", do: <<a>>
|
||||
end
|
||||
end
|
||||
@@ -48,10 +48,25 @@ defmodule Kernel.DialyzerTest do
|
||||
|> String.to_charlist()
|
||||
|
||||
File.cp!(context.base_plt, plt)
|
||||
dialyzer = [analysis_type: :succ_typings, check_plt: false, files_rec: [dir], plts: [plt]]
|
||||
warnings = Map.get(context, :warnings, [])
|
||||
|
||||
dialyzer = [
|
||||
analysis_type: :succ_typings,
|
||||
check_plt: false,
|
||||
files_rec: [dir],
|
||||
plts: [plt],
|
||||
warnings: warnings
|
||||
]
|
||||
|
||||
{:ok, [outdir: dir, dialyzer: dialyzer]}
|
||||
end
|
||||
|
||||
@tag warnings: [:specdiffs]
|
||||
test "no warnings on specdiffs", context do
|
||||
copy_beam!(context, Dialyzer.RemoteCall)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
end
|
||||
|
||||
test "no warnings on valid remote calls", context do
|
||||
copy_beam!(context, Dialyzer.RemoteCall)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
@@ -105,6 +120,11 @@ defmodule Kernel.DialyzerTest do
|
||||
assert_dialyze_no_warnings!(context)
|
||||
end
|
||||
|
||||
test "no warnings on for comprehensions with bitstrings", context do
|
||||
copy_beam!(context, Dialyzer.ForBitstring)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
end
|
||||
|
||||
test "no warnings on for falsy check that always boolean", context do
|
||||
copy_beam!(context, Dialyzer.ForBooleanCheck)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
|
||||
@@ -1945,7 +1945,7 @@ defmodule Kernel.ExpansionTest do
|
||||
message = ~r"literal <<>> in bitstring supports only type specifiers"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<(<<"foo">>::32)>>))
|
||||
expand(quote(do: <<(<<"foo">>)::32>>))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ defmodule Kernel.FnTest do
|
||||
assert (&List.flatten/1).([[0]]) == [0]
|
||||
assert (&List.flatten/1).([[0]]) == [0]
|
||||
assert (&List.flatten(&1)).([[0]]) == [0]
|
||||
assert (&List.flatten(&1)) == &List.flatten/1
|
||||
assert (&List.flatten(&1)) == (&List.flatten/1)
|
||||
end
|
||||
|
||||
test "capture local" do
|
||||
@@ -77,7 +77,7 @@ defmodule Kernel.FnTest do
|
||||
assert (&is_atom/1).(:a)
|
||||
assert (&is_atom/1).(:a)
|
||||
assert (&is_atom(&1)).(:a)
|
||||
assert (&is_atom(&1)) == &is_atom/1
|
||||
assert (&is_atom(&1)) == (&is_atom/1)
|
||||
end
|
||||
|
||||
test "capture macro" do
|
||||
@@ -102,7 +102,7 @@ defmodule Kernel.FnTest do
|
||||
mod = List
|
||||
assert (&mod.flatten(&1)).([1, [2], 3]) == [1, 2, 3]
|
||||
assert (&mod.flatten/1).([1, [2], 3]) == [1, 2, 3]
|
||||
assert (&mod.flatten/1) == &List.flatten/1
|
||||
assert (&mod.flatten/1) == (&List.flatten/1)
|
||||
end
|
||||
|
||||
test "local partial application" do
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Kernel.GuardTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
describe "Kernel.defguard(p) usage" do
|
||||
defmodule Guards.In.Macros do
|
||||
defmodule GuardsInMacros do
|
||||
defguard is_foo(atom) when atom == :foo
|
||||
|
||||
defmacro is_compile_time_foo(atom) when is_foo(atom) do
|
||||
@@ -13,13 +13,13 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
|
||||
test "guards can be used in other macros in the same module" do
|
||||
require Guards.In.Macros
|
||||
assert Guards.In.Macros.is_foo(:foo)
|
||||
refute Guards.In.Macros.is_foo(:baz)
|
||||
assert Guards.In.Macros.is_compile_time_foo(:foo)
|
||||
require GuardsInMacros
|
||||
assert GuardsInMacros.is_foo(:foo)
|
||||
refute GuardsInMacros.is_foo(:baz)
|
||||
assert GuardsInMacros.is_compile_time_foo(:foo)
|
||||
end
|
||||
|
||||
defmodule Guards.In.Funs do
|
||||
defmodule GuardsInFuns do
|
||||
defguard is_foo(atom) when atom == :foo
|
||||
defguard is_equal(foo, bar) when foo == bar
|
||||
|
||||
@@ -29,19 +29,19 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
|
||||
test "guards can be used in other funs in the same module" do
|
||||
require Guards.In.Funs
|
||||
assert Guards.In.Funs.is_foo(:foo)
|
||||
refute Guards.In.Funs.is_foo(:bar)
|
||||
require GuardsInFuns
|
||||
assert GuardsInFuns.is_foo(:foo)
|
||||
refute GuardsInFuns.is_foo(:bar)
|
||||
end
|
||||
|
||||
test "guards do not change code evaluation semantics" do
|
||||
require Guards.In.Funs
|
||||
require GuardsInFuns
|
||||
x = 1
|
||||
assert Guards.In.Funs.is_equal(x = 2, x) == false
|
||||
assert GuardsInFuns.is_equal(x = 2, x) == false
|
||||
assert x == 2
|
||||
end
|
||||
|
||||
defmodule Macros.In.Guards do
|
||||
defmodule MacrosInGuards do
|
||||
defmacro is_foo(atom) do
|
||||
quote do
|
||||
unquote(atom) == :foo
|
||||
@@ -52,35 +52,35 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
|
||||
test "macros can be used in other guards in the same module" do
|
||||
require Macros.In.Guards
|
||||
assert Macros.In.Guards.is_foobar(:foo)
|
||||
assert Macros.In.Guards.is_foobar(:bar)
|
||||
refute Macros.In.Guards.is_foobar(:baz)
|
||||
require MacrosInGuards
|
||||
assert MacrosInGuards.is_foobar(:foo)
|
||||
assert MacrosInGuards.is_foobar(:bar)
|
||||
refute MacrosInGuards.is_foobar(:baz)
|
||||
end
|
||||
|
||||
defmodule Guards.In.Guards do
|
||||
defmodule GuardsInGuards do
|
||||
defguard is_foo(atom) when atom == :foo
|
||||
defguard is_foobar(atom) when is_foo(atom) or atom == :bar
|
||||
end
|
||||
|
||||
test "guards can be used in other guards in the same module" do
|
||||
require Guards.In.Guards
|
||||
assert Guards.In.Guards.is_foobar(:foo)
|
||||
assert Guards.In.Guards.is_foobar(:bar)
|
||||
refute Guards.In.Guards.is_foobar(:baz)
|
||||
require GuardsInGuards
|
||||
assert GuardsInGuards.is_foobar(:foo)
|
||||
assert GuardsInGuards.is_foobar(:bar)
|
||||
refute GuardsInGuards.is_foobar(:baz)
|
||||
end
|
||||
|
||||
defmodule Default.Args do
|
||||
defmodule DefaultArgs do
|
||||
defguard is_divisible(value, remainder \\ 2)
|
||||
when is_integer(value) and rem(value, remainder) == 0
|
||||
end
|
||||
|
||||
test "permits default values in args" do
|
||||
require Default.Args
|
||||
assert Default.Args.is_divisible(2)
|
||||
refute Default.Args.is_divisible(1)
|
||||
assert Default.Args.is_divisible(3, 3)
|
||||
refute Default.Args.is_divisible(3, 4)
|
||||
require DefaultArgs
|
||||
assert DefaultArgs.is_divisible(2)
|
||||
refute DefaultArgs.is_divisible(1)
|
||||
assert DefaultArgs.is_divisible(3, 3)
|
||||
refute DefaultArgs.is_divisible(3, 4)
|
||||
end
|
||||
|
||||
test "doesn't allow matching in args" do
|
||||
@@ -109,7 +109,28 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Integer.Private.Guards do
|
||||
defmodule GuardFromMacro do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "defguard defines a guard from inside another macro" do
|
||||
defmodule UseGuardFromMacro do
|
||||
use GuardFromMacro
|
||||
|
||||
def assert! do
|
||||
assert is_even(0)
|
||||
refute is_even(1)
|
||||
end
|
||||
end
|
||||
|
||||
UseGuardFromMacro.assert!()
|
||||
end
|
||||
|
||||
defmodule IntegerPrivateGuards do
|
||||
defguardp is_even(value) when is_integer(value) and rem(value, 2) == 0
|
||||
|
||||
def is_even_and_large?(value) when is_even(value) and value > 100, do: true
|
||||
@@ -121,19 +142,19 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
|
||||
test "defguardp defines private guards that work inside and outside guard clauses" do
|
||||
assert Integer.Private.Guards.is_even_and_large?(102)
|
||||
refute Integer.Private.Guards.is_even_and_large?(98)
|
||||
refute Integer.Private.Guards.is_even_and_large?(99)
|
||||
refute Integer.Private.Guards.is_even_and_large?(103)
|
||||
assert IntegerPrivateGuards.is_even_and_large?(102)
|
||||
refute IntegerPrivateGuards.is_even_and_large?(98)
|
||||
refute IntegerPrivateGuards.is_even_and_large?(99)
|
||||
refute IntegerPrivateGuards.is_even_and_large?(103)
|
||||
|
||||
assert Integer.Private.Guards.is_even_and_small?(98)
|
||||
refute Integer.Private.Guards.is_even_and_small?(99)
|
||||
refute Integer.Private.Guards.is_even_and_small?(102)
|
||||
refute Integer.Private.Guards.is_even_and_small?(103)
|
||||
assert IntegerPrivateGuards.is_even_and_small?(98)
|
||||
refute IntegerPrivateGuards.is_even_and_small?(99)
|
||||
refute IntegerPrivateGuards.is_even_and_small?(102)
|
||||
refute IntegerPrivateGuards.is_even_and_small?(103)
|
||||
|
||||
assert_raise CompileError, ~r"cannot invoke local is_even/1 inside guard", fn ->
|
||||
defmodule Integer.Private.Guard.Utils do
|
||||
import Integer.Private.Guards
|
||||
defmodule IntegerPrivateGuardUtils do
|
||||
import IntegerPrivateGuards
|
||||
|
||||
def is_even_and_large?(value) when is_even(value) and value > 100, do: true
|
||||
def is_even_and_large?(_), do: false
|
||||
@@ -141,8 +162,8 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
|
||||
assert_raise CompileError, ~r"undefined function is_even/1", fn ->
|
||||
defmodule Integer.Private.Function.Utils do
|
||||
import Integer.Private.Guards
|
||||
defmodule IntegerPrivateFunctionUtils do
|
||||
import IntegerPrivateGuards
|
||||
|
||||
def is_even_and_small?(value) do
|
||||
if is_even(value) and value <= 100, do: true, else: false
|
||||
|
||||
@@ -234,21 +234,21 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
NotAModule
|
||||
Remote.func()
|
||||
R.func()
|
||||
&extract/2
|
||||
&is_record/1
|
||||
&R.func/0
|
||||
&Remote.func/0
|
||||
&Integer.is_even/1
|
||||
_ = &extract/2
|
||||
_ = &is_record/1
|
||||
_ = &R.func/0
|
||||
_ = &Remote.func/0
|
||||
_ = &Integer.is_even/1
|
||||
%Macro.Env{}
|
||||
end
|
||||
|
||||
&extract/2
|
||||
&is_record/1
|
||||
&R.func/0
|
||||
&Remote.func/0
|
||||
&Integer.is_even/1
|
||||
_ = &extract/2
|
||||
_ = &is_record/1
|
||||
_ = &R.func/0
|
||||
_ = &Remote.func/0
|
||||
_ = &Integer.is_even/1
|
||||
|
||||
&is_record/1; def b(a), do: is_record(a)
|
||||
_ = &is_record/1; def b(a), do: is_record(a)
|
||||
|
||||
%Macro.Env{}
|
||||
|
||||
|
||||
@@ -835,8 +835,12 @@ defmodule Kernel.TypespecTest do
|
||||
quote(do: @type(literal_keyword_list_type_key() :: [{binary(), integer()}])),
|
||||
quote(do: @type(literal_empty_map() :: %{})),
|
||||
quote(do: @type(literal_map_with_key() :: %{key: integer()})),
|
||||
quote(do: @type(literal_map_with_required_key() :: %{required(bitstring()) => integer()})),
|
||||
quote(do: @type(literal_map_with_optional_key() :: %{optional(bitstring()) => integer()})),
|
||||
quote(
|
||||
do: @type(literal_map_with_required_key() :: %{required(bitstring()) => integer()})
|
||||
),
|
||||
quote(
|
||||
do: @type(literal_map_with_optional_key() :: %{optional(bitstring()) => integer()})
|
||||
),
|
||||
quote(do: @type(literal_struct_all_fields_any_type() :: %SomeStruct{})),
|
||||
quote(do: @type(literal_struct_all_fields_key_type() :: %SomeStruct{key: integer()})),
|
||||
quote(do: @type(literal_empty_tuple() :: {})),
|
||||
@@ -863,7 +867,9 @@ defmodule Kernel.TypespecTest do
|
||||
quote(do: @type(builtin_list() :: list())),
|
||||
quote(do: @type(builtin_nonempty_list() :: nonempty_list())),
|
||||
quote(do: @type(builtin_maybe_improper_list() :: maybe_improper_list())),
|
||||
quote(do: @type(builtin_nonempty_maybe_improper_list() :: nonempty_maybe_improper_list())),
|
||||
quote(
|
||||
do: @type(builtin_nonempty_maybe_improper_list() :: nonempty_maybe_improper_list())
|
||||
),
|
||||
quote(do: @type(builtin_mfa() :: mfa())),
|
||||
quote(do: @type(builtin_module() :: module())),
|
||||
quote(do: @type(builtin_no_return() :: no_return())),
|
||||
|
||||
@@ -318,16 +318,26 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test "sigil call" do
|
||||
assert Macro.to_string(quote(do: ~r"123")) == ~s/~r"123"/
|
||||
assert Macro.to_string(quote(do: ~r"123"u)) == ~s/~r"123"u/
|
||||
assert Macro.to_string(quote(do: ~r"\n123")) == ~s/~r"\\\\n123"/
|
||||
assert Macro.to_string(quote(do: ~r"123")) == ~S/~r"123"/
|
||||
assert Macro.to_string(quote(do: ~r"123"u)) == ~S/~r"123"u/
|
||||
assert Macro.to_string(quote(do: ~r"\n123")) == ~S/~r"\\n123"/
|
||||
|
||||
assert Macro.to_string(quote(do: ~r"1#{two}3")) == ~S/~r"1#{two}3"/
|
||||
assert Macro.to_string(quote(do: ~r"1#{two}3"u)) == ~S/~r"1#{two}3"u/
|
||||
|
||||
assert Macro.to_string(quote(do: ~R"123")) == ~s/~R"123"/
|
||||
assert Macro.to_string(quote(do: ~R"123"u)) == ~s/~R"123"u/
|
||||
assert Macro.to_string(quote(do: ~R"\n123")) == ~s/~R"\\\\n123"/
|
||||
assert Macro.to_string(quote(do: ~R"123")) == ~S/~R"123"/
|
||||
assert Macro.to_string(quote(do: ~R"123"u)) == ~S/~R"123"u/
|
||||
assert Macro.to_string(quote(do: ~R"\n123")) == ~S/~R"\n123"/
|
||||
|
||||
assert Macro.to_string(quote(do: ~S["'(123)'"])) == ~S/~S["'(123)'"]/
|
||||
|
||||
assert Macro.to_string(
|
||||
quote do
|
||||
~S"""
|
||||
"123"
|
||||
"""
|
||||
end
|
||||
) == ~s[~S"""\n"123"\n"""]
|
||||
end
|
||||
|
||||
test "tuple call" do
|
||||
@@ -586,6 +596,7 @@ defmodule MacroTest do
|
||||
assert Macro.to_string(quote(do: %Test{foo: 1, bar: 1})) == "%Test{foo: 1, bar: 1}"
|
||||
assert Macro.to_string(quote(do: %Test{struct | foo: 2})) == "%Test{struct | foo: 2}"
|
||||
assert Macro.to_string(quote(do: %Test{} + 1)) == "%Test{} + 1"
|
||||
assert Macro.to_string(quote(do: %Test{foo(1)} + 2)) == "%Test{foo(1)} + 2"
|
||||
end
|
||||
|
||||
test "binary operators" do
|
||||
|
||||
@@ -323,6 +323,19 @@ defmodule TaskTest do
|
||||
assert Task.yield_many([task1, task2, task3], 0) ==
|
||||
[{task1, {:ok, :result}}, {task2, nil}, {task3, {:exit, :normal}}]
|
||||
end
|
||||
|
||||
test "returns results on infinity timeout" do
|
||||
task1 = %Task{ref: make_ref(), owner: self()}
|
||||
task2 = %Task{ref: make_ref(), owner: self()}
|
||||
task3 = %Task{ref: make_ref(), owner: self()}
|
||||
|
||||
send(self(), {task1.ref, :result})
|
||||
send(self(), {task2.ref, :result})
|
||||
send(self(), {:DOWN, task3.ref, :process, self(), :normal})
|
||||
|
||||
assert Task.yield_many([task1, task2, task3], :infinity) ==
|
||||
[{task1, {:ok, :result}}, {task2, {:ok, :result}}, {task3, {:exit, :normal}}]
|
||||
end
|
||||
end
|
||||
|
||||
describe "shutdown/2" do
|
||||
|
||||
@@ -306,6 +306,10 @@ defmodule URITest do
|
||||
assert URI.parse("http://example.com/#").fragment == ""
|
||||
assert URI.parse("http://example.com/test#").fragment == ""
|
||||
end
|
||||
|
||||
test "preserves an empty query" do
|
||||
assert URI.parse("http://foo.com/?").query == ""
|
||||
end
|
||||
end
|
||||
|
||||
test "default_port/1,2" do
|
||||
|
||||
@@ -151,6 +151,10 @@ string_test() ->
|
||||
[{bin_string, {1, 1, nil}, [<<"f\"">>]}] = tokenize("\"f\\\"\""),
|
||||
[{list_string, {1, 1, nil}, [<<"foo">>]}] = tokenize("'foo'").
|
||||
|
||||
heredoc_test() ->
|
||||
[{bin_heredoc, {1, 1, nil}, [<<"heredoc\n">>]}] = tokenize("\"\"\"\nheredoc\n\"\"\""),
|
||||
[{bin_heredoc, {1, 1, nil}, [<<"heredoc\n">>]}, {';', {3, 5, 0}}] = tokenize("\"\"\"\n heredoc\n \"\"\";").
|
||||
|
||||
empty_string_test() ->
|
||||
[{bin_string, {1, 1, nil}, [<<>>]}] = tokenize("\"\""),
|
||||
[{list_string, {1, 1, nil}, [<<>>]}] = tokenize("''").
|
||||
@@ -204,6 +208,12 @@ sigil_terminator_test() ->
|
||||
[{sigil, {1, 1, nil}, 114, [<<"foo">>], [], <<"/">>}] = tokenize("~r/foo/"),
|
||||
[{sigil, {1, 1, nil}, 114, [<<"foo">>], [], <<"[">>}] = tokenize("~r[foo]"),
|
||||
[{sigil, {1, 1, nil}, 114, [<<"foo">>], [], <<"\"">>}] = tokenize("~r\"foo\""),
|
||||
[{sigil, {1, 1, nil}, 114, [<<"foo">>], [], <<"/">>},
|
||||
{comp_op, {1, 9, nil}, '=='},
|
||||
{identifier, {1, 12, nil}, bar}] = tokenize("~r/foo/ == bar"),
|
||||
[{sigil, {1, 1, nil}, 114, [<<"foo">>], "iu", <<"/">>},
|
||||
{comp_op, {1, 11, nil}, '=='},
|
||||
{identifier, {1, 14, nil}, bar}] = tokenize("~r/foo/iu == bar"),
|
||||
[{sigil, {1, 1, nil}, 83, [<<"sigil heredoc\n">>], [], <<"\"\"\"">>}] = tokenize("~S\"\"\"\nsigil heredoc\n\"\"\""),
|
||||
[{sigil, {1, 1, nil}, 83, [<<"sigil heredoc\n">>], [], <<"'''">>}] = tokenize("~S'''\nsigil heredoc\n'''").
|
||||
|
||||
|
||||
+1
-1
@@ -643,7 +643,7 @@ defmodule IEx do
|
||||
By default, the number of stops in a breakpoint is 1. Any follow-up
|
||||
call won't stop the code execution unless another breakpoint is set.
|
||||
|
||||
Alternatively, the number of be increased by passing the `stops`
|
||||
Alternatively, the number of stops can be increased by passing the `stops`
|
||||
argument. `IEx.Helpers.reset_break/1` and `IEx.Helpers.reset_break/3`
|
||||
can be used to reset the number back to zero. Note the module remains
|
||||
"instrumented" even after all stops on all breakpoints are consumed.
|
||||
|
||||
@@ -506,7 +506,8 @@ defmodule Logger do
|
||||
:sync_threshold,
|
||||
:truncate,
|
||||
:level,
|
||||
:utc_log
|
||||
:utc_log,
|
||||
:discard_threshold
|
||||
]
|
||||
@spec configure(keyword) :: :ok
|
||||
def configure(options) do
|
||||
|
||||
@@ -28,8 +28,10 @@ defmodule Logger.App do
|
||||
|
||||
case Supervisor.start_link(children, strategy: :rest_for_one, name: Logger.Supervisor) do
|
||||
{:ok, sup} ->
|
||||
handlers = [error_logger_tty_h: otp_reports?, sasl_report_tty_h: sasl_reports?]
|
||||
delete_handlers(handlers)
|
||||
if otp_reports? or sasl_reports? do
|
||||
delete_handlers(otp_reports?, sasl_reports?)
|
||||
end
|
||||
|
||||
{:ok, sup, config}
|
||||
|
||||
{:error, _} = error ->
|
||||
@@ -45,9 +47,7 @@ defmodule Logger.App do
|
||||
|
||||
@doc false
|
||||
def stop(config) do
|
||||
Logger.Config.deleted_handlers()
|
||||
|> add_handlers()
|
||||
|
||||
add_handlers(Logger.Config.deleted_handlers())
|
||||
Logger.Config.delete(config)
|
||||
end
|
||||
|
||||
@@ -73,17 +73,33 @@ defmodule Logger.App do
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_handlers(handlers) do
|
||||
to_delete =
|
||||
for {handler, delete?} <- handlers,
|
||||
delete? && :error_logger.delete_report_handler(handler) != {:error, :module_not_found},
|
||||
do: handler
|
||||
defp delete_handlers(otp_reports?, sasl_reports?) do
|
||||
deleted =
|
||||
if is_pid(Process.whereis(:logger)) and Code.ensure_loaded?(:logger) do
|
||||
with {:ok, {module, config}} <- :logger.get_handler_config(:logger_std_h),
|
||||
:ok <- :logger.remove_handler(:logger_std_h) do
|
||||
[{:logger_std_h, module, config}]
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
else
|
||||
for {tty, true} <- [error_logger_tty_h: otp_reports?, sasl_report_tty_h: sasl_reports?],
|
||||
:error_logger.delete_report_handler(tty) != {:error, :module_not_found},
|
||||
do: tty
|
||||
end
|
||||
|
||||
[] = Logger.Config.deleted_handlers(to_delete)
|
||||
[] = Logger.Config.deleted_handlers(deleted)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp add_handlers(handlers) do
|
||||
Enum.each(handlers, &:error_logger.add_report_handler/1)
|
||||
for handler <- handlers do
|
||||
case handler do
|
||||
{handler, module, config} -> :logger.add_handler(handler, module, config)
|
||||
handler -> :error_logger.add_report_handler(handler)
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +20,13 @@ defmodule Logger.Watcher do
|
||||
def init({mod, handler, args}) do
|
||||
Process.flag(:trap_exit, true)
|
||||
|
||||
# This is required for OTP 21. A better fix would be to not include
|
||||
# the error_logger handler in the first place but we will do so only
|
||||
# in future Elixir versions.
|
||||
unless Process.whereis(mod) do
|
||||
_ = :logger.add_handler(mod, mod, %{level: :info, filter_default: :log})
|
||||
end
|
||||
|
||||
case :gen_event.delete_handler(mod, handler, :ok) do
|
||||
{:error, :module_not_found} ->
|
||||
case :gen_event.add_sup_handler(mod, handler, args) do
|
||||
|
||||
@@ -731,6 +731,23 @@ defmodule Logger.TranslatorTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates named DynamicSupervisor reports abnormal shutdown" do
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
|
||||
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one, name: __MODULE__)
|
||||
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
|
||||
Process.exit(pid, :normal)
|
||||
receive do: ({:EXIT, ^pid, _} -> :ok)
|
||||
Process.flag(:trap_exit, trap)
|
||||
end) =~ ~r"""
|
||||
\[error\] Child :undefined of Supervisor Logger.TranslatorTest shutdown abnormally
|
||||
\*\* \(exit\) :stop
|
||||
Pid: #PID<\d+\.\d+\.\d+>
|
||||
Start Call: Logger.TranslatorTest.abnormal\(\)
|
||||
"""
|
||||
end
|
||||
|
||||
test "translates :supervisor_bridge progress" do
|
||||
assert capture_log(:info, fn ->
|
||||
trap = Process.flag(:trap_exit, true)
|
||||
|
||||
@@ -348,4 +348,21 @@ defmodule LoggerTest do
|
||||
Logger.App.stop()
|
||||
Application.start(:logger)
|
||||
end
|
||||
|
||||
test "configure/1 sets options" do
|
||||
Logger.configure(sync_threshold: 10)
|
||||
Logger.configure(truncate: 4048)
|
||||
Logger.configure(utc_log: true)
|
||||
Logger.configure(discard_threshold: 10_000)
|
||||
logger_data = Logger.Config.__data__()
|
||||
assert Map.get(logger_data, :sync_threshold) == 10
|
||||
assert Map.get(logger_data, :truncate) == 4048
|
||||
assert Map.get(logger_data, :utc_log) == true
|
||||
assert Map.get(logger_data, :discard_threshold) == 10_000
|
||||
after
|
||||
Logger.configure(sync_threshold: 20)
|
||||
Logger.configure(sync_threshold: 8096)
|
||||
Logger.configure(utc_log: false)
|
||||
Logger.configure(discard_threshold: 500)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,6 +142,19 @@ defmodule Mix.Tasks.Format do
|
||||
|> check!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns formatter options to be used for the given file.
|
||||
"""
|
||||
def formatter_opts_for_file(file, opts \\ []) do
|
||||
{dot_formatter, formatter_opts} = eval_dot_formatter(opts)
|
||||
|
||||
{formatter_opts_and_subs, _sources} =
|
||||
eval_deps_and_subdirectories(dot_formatter, [], formatter_opts, [dot_formatter])
|
||||
|
||||
split = file |> Path.relative_to_cwd() |> Path.split()
|
||||
find_formatter_opts_for_file(split, formatter_opts_and_subs)
|
||||
end
|
||||
|
||||
defp eval_dot_formatter(opts) do
|
||||
cond do
|
||||
dot_formatter = opts[:dot_formatter] ->
|
||||
@@ -308,7 +321,7 @@ defmodule Mix.Tasks.Format do
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
||||
defp expand_args(files_and_patterns, _dot_formatter, formatter_opts_and_subs) do
|
||||
defp expand_args(files_and_patterns, _dot_formatter, {formatter_opts, subs}) do
|
||||
files =
|
||||
for file_or_pattern <- files_and_patterns,
|
||||
file <- stdin_or_wildcard(file_or_pattern),
|
||||
@@ -324,10 +337,10 @@ defmodule Mix.Tasks.Format do
|
||||
|
||||
for file <- files do
|
||||
if file == :stdin do
|
||||
{file, []}
|
||||
{file, formatter_opts}
|
||||
else
|
||||
split = file |> Path.relative_to_cwd() |> Path.split()
|
||||
{file, find_formatter_opts_for_file(split, formatter_opts_and_subs)}
|
||||
{file, find_formatter_opts_for_file(split, {formatter_opts, subs})}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.Xref do
|
||||
Prints a file dependency graph where an edge from `A` to `B` indicates
|
||||
that `A` depends on `B`.
|
||||
|
||||
mix xref graph --format dot
|
||||
mix xref graph --format stats
|
||||
|
||||
The following options are accepted:
|
||||
|
||||
|
||||
@@ -90,6 +90,23 @@ defmodule Mix.Tasks.FormatTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "reads file from stdin and prints to stdout with formatter", context do
|
||||
in_tmp(context.test, fn ->
|
||||
File.write!(".formatter.exs", """
|
||||
[locals_without_parens: [foo: 1]]
|
||||
""")
|
||||
|
||||
output =
|
||||
capture_io("foo :bar", fn ->
|
||||
Mix.Tasks.Format.run(["-"])
|
||||
end)
|
||||
|
||||
assert output == """
|
||||
foo :bar
|
||||
"""
|
||||
end)
|
||||
end
|
||||
|
||||
test "checks if file is formatted with --check-formatted", context do
|
||||
in_tmp(context.test, fn ->
|
||||
File.write!("a.ex", """
|
||||
@@ -196,6 +213,9 @@ defmodule Mix.Tasks.FormatTest do
|
||||
[inputs: "a.ex", locals_without_parens: [my_fun: 2]]
|
||||
""")
|
||||
|
||||
formatter_opts = Mix.Tasks.Format.formatter_opts_for_file("lib/extra/a.ex")
|
||||
assert [my_fun: 2] = Keyword.get(formatter_opts, :locals_without_parens)
|
||||
|
||||
File.write!("lib/a.ex", """
|
||||
my_fun :foo, :bar
|
||||
other_fun :baz
|
||||
@@ -263,6 +283,9 @@ defmodule Mix.Tasks.FormatTest do
|
||||
# Let's check that the manifest gets updated if it's stale.
|
||||
File.touch!(manifest_path, {{1970, 1, 1}, {0, 0, 0}})
|
||||
|
||||
formatter_opts = Mix.Tasks.Format.formatter_opts_for_file("a.ex")
|
||||
assert [my_fun: 2] = Keyword.get(formatter_opts, :locals_without_parens)
|
||||
|
||||
Mix.Tasks.Format.run(["a.ex"])
|
||||
assert File.stat!(manifest_path).mtime > {{1970, 1, 1}, {0, 0, 0}}
|
||||
end)
|
||||
@@ -340,6 +363,9 @@ defmodule Mix.Tasks.FormatTest do
|
||||
File.touch!("lib/extra/.formatter.exs", {{2030, 1, 1}, {0, 0, 0}})
|
||||
Mix.Tasks.Format.run([])
|
||||
|
||||
formatter_opts = Mix.Tasks.Format.formatter_opts_for_file("lib/extra/a.ex")
|
||||
assert [other_fun: 1] = Keyword.get(formatter_opts, :locals_without_parens)
|
||||
|
||||
assert File.read!("lib/extra/a.ex") == """
|
||||
my_fun(:foo, :bar)
|
||||
other_fun :baz
|
||||
|
||||
Reference in New Issue
Block a user