Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ad182986c | ||
|
|
16292c65ba | ||
|
|
d25ce48d5d | ||
|
|
66b4ab19ae | ||
|
|
01a4889e39 | ||
|
|
d5e7446399 | ||
|
|
ab1da111bf | ||
|
|
6549b00eba | ||
|
|
4431ac63e2 | ||
|
|
280215c24f | ||
|
|
071285403d | ||
|
|
3d3453c1e2 | ||
|
|
2dc3fd26b8 | ||
|
|
e61ed47066 |
@@ -128,6 +128,27 @@ Now any application can use your formatter as follows:
|
||||
|
||||
Finally, the `Code` module has also been augmented with two functions: `Code.string_to_quoted_with_comments/2` and `Code.quoted_to_algebra/2`. Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
|
||||
|
||||
## v1.13.2 (2022-01-13)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Allow plugins to also format `.ex` and `.exs` files
|
||||
* [mix release] Allow bypassing application mode validation in release spec
|
||||
* [mix test] Print a message when the suite fails due to the coverage threshold
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Do not emit warnings on `Code.Fragment.container_cursor_to_quoted/2`
|
||||
* [Kernel] Fix a crash when a for-comprehension with `:uniq` was used inside another comprehension with `:uniq`
|
||||
* [Kernel] Ensure `env.context_modules` is properly set inside optimized `defmodule`
|
||||
* [Keyword] Deprecate the recently added `Keyword.map/2` as it is equivalent to `Keyword.new/2`
|
||||
* [Map] Deprecate the recently added `Map.map/2` as it is equivalent to `Map.new/2`
|
||||
* [Protocol] Warn on zero arity callbacks inside protocols
|
||||
|
||||
## v1.13.1 (2021-12-14)
|
||||
|
||||
### 1. Bug fixes
|
||||
@@ -210,6 +231,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
|
||||
#### Logger
|
||||
|
||||
* [Logger] Add `Logger.put_application_level/2`
|
||||
* [Logger] Print all log levels in accordance to Erlang/OTP. This also means `[warn]` is now shown as `[warning]`
|
||||
|
||||
#### Mix
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.13.1
|
||||
ELIXIR_VERSION=1.13.2
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.13.1
|
||||
set ELIXIR_VERSION=1.13.2
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -827,7 +827,7 @@ defmodule Code.Fragment do
|
||||
|
||||
case :elixir_tokenizer.tokenize(fragment, line, column, tokenizer_opts) do
|
||||
{:ok, _, _, _warnings, tokens} ->
|
||||
:elixir.tokens_to_quoted(tokens, file, columns: columns, token_metadata: token_metadata)
|
||||
:elixir.tokens_to_quoted(tokens, nil, columns: columns, token_metadata: token_metadata)
|
||||
|
||||
{:error, {line, column, {prefix, suffix}, token}, _rest, _warnings, _so_far} ->
|
||||
location = [line: line, column: column]
|
||||
|
||||
@@ -1374,27 +1374,9 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps the function `fun` over all key-value pairs in `keywords`,
|
||||
returning a keyword list with all the values replaced with
|
||||
the result of the function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.map([one: 1, two: 2, three: 3], fn {_key, val} -> to_string(val) end)
|
||||
[one: "1", two: "2", three: "3"]
|
||||
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec map(t, ({key, value} -> value)) :: t
|
||||
def map(keywords, fun) when is_list(keywords) and is_function(fun, 1) do
|
||||
do_map(keywords, fun)
|
||||
end
|
||||
|
||||
defp do_map([], _fun), do: []
|
||||
|
||||
defp do_map([{key, value} | rest], fun) do
|
||||
new_value = fun.({key, value})
|
||||
[{key, new_value} | do_map(rest, fun)]
|
||||
@doc false
|
||||
@deprecated "Use Keyword.new/2 instead"
|
||||
def map(keywords, fun) when is_list(keywords) do
|
||||
Enum.map(keywords, fn {k, v} -> {k, fun.({k, v})} end)
|
||||
end
|
||||
end
|
||||
|
||||
+6
-32
@@ -982,8 +982,8 @@ defmodule Map do
|
||||
See also `reject/2` which discards all elements where the
|
||||
function returns a truthy value.
|
||||
|
||||
> Note: if you find yourself doing multiple calls to `Map.map/2`
|
||||
> and `Map.filter/2` in a pipeline, it is likely more efficient
|
||||
> Note: if you find yourself doing multiple calls to `Map.filter/2`
|
||||
> and `Map.reject/2` in a pipeline, it is likely more efficient
|
||||
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
|
||||
> a map at the end using `Map.new/1`.
|
||||
|
||||
@@ -1041,35 +1041,9 @@ defmodule Map do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps the function `fun` over all key-value pairs in `map`
|
||||
|
||||
It returns a map with all the values replaced with the result
|
||||
of the function.
|
||||
|
||||
> Note: if you find yourself doing multiple calls to `Map.map/2`
|
||||
> and `Map.filter/2` in a pipeline, it is likely more efficient
|
||||
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
|
||||
> a map at the end using `Map.new/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.map(%{1 => "joe", 2 => "mike", 3 => "robert"}, fn {_key, val} -> String.capitalize(val) end)
|
||||
%{1 => "Joe", 2 => "Mike", 3 => "Robert"}
|
||||
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec map(map, ({key, value} -> value)) :: map
|
||||
def map(map, fun) when is_map(map) and is_function(fun, 1) do
|
||||
iter = :maps.iterator(map)
|
||||
next = :maps.next(iter)
|
||||
:maps.from_list(do_map(next, fun))
|
||||
end
|
||||
|
||||
defp do_map(:none, _fun), do: []
|
||||
|
||||
defp do_map({key, value, iter}, fun) do
|
||||
new_value = fun.({key, value})
|
||||
[{key, new_value} | do_map(:maps.next(iter), fun)]
|
||||
@doc false
|
||||
@deprecated "Use Map.new/2 instead"
|
||||
def map(map, fun) when is_map(map) do
|
||||
new(map, fn {k, v} -> {k, fun.({k, v})} end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -383,7 +383,7 @@ defmodule Process do
|
||||
@type spawn_opt ::
|
||||
:link
|
||||
| :monitor
|
||||
| {:monitor, :erlang.monitor_option()}
|
||||
| {:monitor, monitor_option()}
|
||||
| {:priority, :low | :normal | :high}
|
||||
| {:fullsweep_after, non_neg_integer}
|
||||
| {:min_heap_size, non_neg_integer}
|
||||
@@ -392,6 +392,10 @@ defmodule Process do
|
||||
| {:message_queue_data, :off_heap | :on_heap}
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
# TODO: Use :erlang.monitor_option() on Erlang/OTP 24+
|
||||
@typep monitor_option ::
|
||||
[alias: :explicit_unalias | :demonitor | :reply_demonitor, tag: term()]
|
||||
|
||||
@doc """
|
||||
Spawns the given function according to the given options.
|
||||
|
||||
|
||||
@@ -723,14 +723,14 @@ defmodule Protocol do
|
||||
|
||||
defp callback_ast_to_fa({kind, {:"::", meta, [{name, _, args}, _return]}, _pos})
|
||||
when kind in [:callback, :macrocallback] do
|
||||
[{{name, length(args)}, meta}]
|
||||
[{{name, length(List.wrap(args))}, meta}]
|
||||
end
|
||||
|
||||
defp callback_ast_to_fa(
|
||||
{kind, {:when, _, [{:"::", meta, [{name, _, args}, _return]}, _vars]}, _pos}
|
||||
)
|
||||
when kind in [:callback, :macrocallback] do
|
||||
[{{name, length(args)}, meta}]
|
||||
[{{name, length(List.wrap(args))}, meta}]
|
||||
end
|
||||
|
||||
defp callback_ast_to_fa({kind, _, _pos}) when kind in [:callback, :macrocallback] do
|
||||
|
||||
@@ -359,8 +359,8 @@ string_to_tokens(String, StartLine, StartColumn, File, Opts) when is_integer(Sta
|
||||
{error, {Location, to_binary(Error), to_binary(Token)}}
|
||||
end.
|
||||
|
||||
tokens_to_quoted(Tokens, File, Opts) ->
|
||||
handle_parsing_opts(File, Opts),
|
||||
tokens_to_quoted(Tokens, WarningFile, Opts) ->
|
||||
handle_parsing_opts(WarningFile, Opts),
|
||||
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ok, Forms} ->
|
||||
@@ -370,7 +370,7 @@ tokens_to_quoted(Tokens, File, Opts) ->
|
||||
{error, {Line, _, [Error, Token]}} ->
|
||||
{error, {parser_location(Line), to_binary(Error), to_binary(Token)}}
|
||||
after
|
||||
erase(elixir_parser_file),
|
||||
erase(elixir_parser_warning_file),
|
||||
erase(elixir_parser_columns),
|
||||
erase(elixir_token_metadata),
|
||||
erase(elixir_literal_encoder)
|
||||
@@ -406,7 +406,7 @@ parser_location(Meta) ->
|
||||
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
|
||||
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||
|
||||
handle_parsing_opts(File, Opts) ->
|
||||
handle_parsing_opts(WarningFile, Opts) ->
|
||||
LiteralEncoder =
|
||||
case lists:keyfind(literal_encoder, 1, Opts) of
|
||||
{literal_encoder, Fun} -> Fun;
|
||||
@@ -414,7 +414,7 @@ handle_parsing_opts(File, Opts) ->
|
||||
end,
|
||||
TokenMetadata = lists:keyfind(token_metadata, 1, Opts) == {token_metadata, true},
|
||||
Columns = lists:keyfind(columns, 1, Opts) == {columns, true},
|
||||
put(elixir_parser_file, File),
|
||||
put(elixir_parser_warning_file, WarningFile),
|
||||
put(elixir_parser_columns, Columns),
|
||||
put(elixir_token_metadata, TokenMetadata),
|
||||
put(elixir_literal_encoder, LiteralEncoder).
|
||||
|
||||
@@ -133,7 +133,8 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
||||
'Elixir.Macro':expand(Mod, E)
|
||||
end,
|
||||
|
||||
elixir_module:compile(Expanded, Block, [], E).
|
||||
ContextModules = [Expanded | ?key(E, context_modules)],
|
||||
elixir_module:compile(Expanded, Block, [], E#{context_modules := ContextModules}).
|
||||
|
||||
%% Bootstrapper
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
|
||||
({'case', CaseAnn, _, CaseBlock}, InnerAcc) -> {'case', CaseAnn, InnerAcc, CaseBlock}
|
||||
end,
|
||||
|
||||
{build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE), SE}.
|
||||
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
|
||||
|
||||
translate_into(Meta, Cases, Expr, Opts, Return, S) ->
|
||||
Ann = ?ann(Meta),
|
||||
@@ -115,11 +115,11 @@ build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
|
||||
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), S};
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, 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),
|
||||
{?remote(Ann, lists, reverse, [ReduceExpr]), S};
|
||||
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
|
||||
{?remote(Ann, lists, reverse, [ReduceExpr]), SR};
|
||||
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
{InnerValue, SV} = build_var(Ann, S),
|
||||
Generated = erl_anno:set_generated(true, Ann),
|
||||
@@ -138,8 +138,8 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
]}
|
||||
end,
|
||||
|
||||
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
|
||||
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SV}.
|
||||
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
|
||||
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SR}.
|
||||
|
||||
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
|
||||
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
|
||||
@@ -161,7 +161,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
?remote(Ann, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
|
||||
IntoReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
|
||||
{IntoReduceExpr, SN} = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
|
||||
|
||||
TryExpr =
|
||||
{'try', Ann,
|
||||
@@ -173,7 +173,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
[stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack)],
|
||||
[]},
|
||||
|
||||
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
|
||||
{{block, Ann, [MatchExpr, TryExpr]}, SN}.
|
||||
|
||||
stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
|
||||
{clause, Ann,
|
||||
@@ -186,7 +186,7 @@ stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
|
||||
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, S) ->
|
||||
{Acc, SA} = build_var(Ann, S),
|
||||
build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA);
|
||||
{build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA), SA};
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
|
||||
%% Those variables are used only inside the anonymous function
|
||||
%% so we don't need to worry about returning the scope.
|
||||
@@ -209,7 +209,7 @@ build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
|
||||
]},
|
||||
|
||||
EnumReduceCall = build_reduce_each(Clauses, InnerExpr, NewInto, Acc, SU),
|
||||
?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]).
|
||||
{?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]), SU}.
|
||||
|
||||
build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
|
||||
Ann = ?ann(Meta),
|
||||
|
||||
@@ -640,7 +640,6 @@ map -> struct_op struct_expr eol map_args : {'%', meta_from_token('$1'), ['$2',
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(file(), get(elixir_parser_file)).
|
||||
-define(columns(), get(elixir_parser_columns)).
|
||||
-define(token_metadata(), get(elixir_token_metadata)).
|
||||
|
||||
@@ -729,10 +728,12 @@ build_op(AST, {_Kind, Location, '//'}, Right) ->
|
||||
|
||||
build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
|
||||
%% TODO: Remove "not left in right" rearrangement on v2.0
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
"\"not expr1 in expr2\" is deprecated. "
|
||||
"Instead use \"expr1 not in expr2\" if you require Elixir v1.5+, "
|
||||
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"),
|
||||
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"
|
||||
),
|
||||
Meta = meta_from_location(Location),
|
||||
{UOp, Meta, [{'in', Meta, [Left, Right]}]};
|
||||
|
||||
@@ -1169,21 +1170,22 @@ error_invalid_kw_identifier({_, Location, KW}) ->
|
||||
|
||||
%% TODO: Make this an error on v2.0
|
||||
warn_trailing_comma({',', {Line, Column, _}}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
"trailing commas are not allowed inside function/macro call arguments"
|
||||
).
|
||||
warn({Line, Column}, "trailing commas are not allowed inside function/macro call arguments").
|
||||
|
||||
%% TODO: Make this an error on v2.0
|
||||
warn_empty_paren({_, {Line, Column, _}}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
"invalid expression (). "
|
||||
"If you want to invoke or define a function, make sure there are "
|
||||
"no spaces between the function name and its arguments. If you wanted "
|
||||
"to pass an empty block or code, pass a value instead, such as a nil or an atom").
|
||||
"to pass an empty block or code, pass a value instead, such as a nil or an atom"
|
||||
).
|
||||
|
||||
%% TODO: Make this an error on v2.0
|
||||
warn_pipe({arrow_op, {Line, Column, _}, Op}, {_, [_ | _], [_ | _]}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
io_lib:format(
|
||||
"parentheses are required when piping into a function call. For example:\n\n"
|
||||
" foo 1 ~ts bar 2 ~ts baz 3\n\n"
|
||||
@@ -1197,6 +1199,14 @@ warn_pipe(_Token, _) ->
|
||||
ok.
|
||||
|
||||
warn_empty_stab_clause({stab_op, {Line, Column, _}, '->'}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
"an expression is always required on the right side of ->. "
|
||||
"Please provide a value after ->").
|
||||
"Please provide a value after ->"
|
||||
).
|
||||
|
||||
warn(LineColumn, Message) ->
|
||||
case get(elixir_parser_warning_file) of
|
||||
nil -> ok;
|
||||
File -> elixir_errors:erl_warn(LineColumn, File, Message)
|
||||
end.
|
||||
@@ -915,6 +915,11 @@ defmodule CodeFragmentTest do
|
||||
assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
|
||||
end
|
||||
|
||||
test "no warnings" do
|
||||
assert cc2q(~s"?\\ ") == s2q("__cursor__()")
|
||||
assert cc2q(~s"{fn -> end, ") == s2q("{fn -> nil end, __cursor__()}")
|
||||
end
|
||||
|
||||
test "options" do
|
||||
opts = [columns: true]
|
||||
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
|
||||
|
||||
@@ -98,6 +98,19 @@ defmodule Kernel.ComprehensionTest do
|
||||
assert Process.get(:into_halt)
|
||||
end
|
||||
|
||||
test "nested for comprehensions with unique values" do
|
||||
assert for(x <- [1, 1, 2], uniq: true, do: for(y <- [3, 3], uniq: true, do: x * y)) == [
|
||||
[3],
|
||||
[6]
|
||||
]
|
||||
|
||||
assert for(<<x <- "abcabc">>,
|
||||
uniq: true,
|
||||
into: "",
|
||||
do: for(<<y <- "zz">>, uniq: true, into: "", do: to_bin(x) <> to_bin(y))
|
||||
) == "azbzcz"
|
||||
end
|
||||
|
||||
test "for comprehensions with nilly filters" do
|
||||
assert for(x <- 1..3, nilly(), do: x * 2) == []
|
||||
end
|
||||
|
||||
@@ -1163,9 +1163,11 @@ defmodule Kernel.WarningTest do
|
||||
|
||||
def without_specs(term, options \\ [])
|
||||
|
||||
@callback foo :: {:ok, term}
|
||||
@callback foo(term) :: {:ok, term}
|
||||
@callback foo(term, keyword) :: {:ok, term, keyword}
|
||||
|
||||
@callback foo_when :: {:ok, x} when x: term
|
||||
@callback foo_when(x) :: {:ok, x} when x: term
|
||||
@callback foo_when(x, opts) :: {:ok, x, opts} when x: term, opts: keyword
|
||||
|
||||
@@ -1178,12 +1180,18 @@ defmodule Kernel.WarningTest do
|
||||
""")
|
||||
end)
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo_when/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo_when/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
|
||||
@@ -810,6 +810,14 @@ defmodule MacroTest do
|
||||
defmodule Foo.Bar do
|
||||
assert __MODULE__ in __ENV__.context_modules
|
||||
end
|
||||
|
||||
assert Foo.Bar in __ENV__.context_modules
|
||||
|
||||
Code.compile_string("""
|
||||
defmodule Foo.Bar.Compiled do
|
||||
true = __MODULE__ in __ENV__.context_modules
|
||||
end
|
||||
""")
|
||||
end
|
||||
|
||||
test "to_match/1" do
|
||||
|
||||
@@ -609,13 +609,24 @@ defmodule Mix.Release do
|
||||
end
|
||||
|
||||
defp build_release_spec(release, modes) do
|
||||
%{name: name, version: version, erts_version: erts_version, applications: apps} = release
|
||||
%{
|
||||
name: name,
|
||||
version: version,
|
||||
erts_version: erts_version,
|
||||
applications: apps,
|
||||
options: options
|
||||
} = release
|
||||
|
||||
skip_mode_validation_for =
|
||||
options
|
||||
|> Keyword.get(:skip_mode_validation_for, [])
|
||||
|> MapSet.new()
|
||||
|
||||
rel_apps =
|
||||
for {app, mode} <- modes do
|
||||
properties = Map.get(apps, app) || throw({:error, "Unknown application #{inspect(app)}"})
|
||||
children = Keyword.get(properties, :applications, [])
|
||||
validate_mode!(app, mode, modes, children)
|
||||
app in skip_mode_validation_for || validate_mode!(app, mode, modes, children)
|
||||
build_app_for_release(app, mode, properties)
|
||||
end
|
||||
|
||||
|
||||
@@ -495,12 +495,12 @@ defmodule Mix.Tasks.Format do
|
||||
ext = Path.extname(file)
|
||||
|
||||
cond do
|
||||
ext in ~w(.ex .exs) ->
|
||||
&elixir_format(&1, [file: file] ++ formatter_opts)
|
||||
|
||||
plugin = find_plugin_for_extension(formatter_opts, ext) ->
|
||||
&plugin.format(&1, [extension: ext] ++ formatter_opts)
|
||||
|
||||
ext in ~w(.ex .exs) ->
|
||||
&elixir_format(&1, [file: file] ++ formatter_opts)
|
||||
|
||||
true ->
|
||||
& &1
|
||||
end
|
||||
|
||||
@@ -455,6 +455,14 @@ defmodule Mix.Tasks.Release do
|
||||
* `:steps` - a list of steps to execute when assembling the release. See
|
||||
the "Steps" section for more information.
|
||||
|
||||
* `:skip_mode_validation_for` - a list of application names
|
||||
(atoms) specifying applications to skip strict validation of
|
||||
"unsafe" modes. An "unsafe" case is when a parent application
|
||||
mode is `:permanent` but one of the applications it depends on
|
||||
is set to `:load`. Use this with care, as a release with
|
||||
invalid modes may no longer boot without additional tweaks.
|
||||
Defaults to `[]`.
|
||||
|
||||
Note each release definition can be given as an anonymous function. This
|
||||
is useful if some release attributes are expensive to compute:
|
||||
|
||||
|
||||
@@ -299,6 +299,7 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
print_summary(module_results, totals, summary_opts)
|
||||
|
||||
if totals < get_threshold(summary_opts) do
|
||||
print_failed_threshold(totals, get_threshold(summary_opts))
|
||||
System.at_exit(fn _ -> exit({:shutdown, 3}) end)
|
||||
end
|
||||
|
||||
@@ -353,6 +354,13 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
Mix.shell().info("")
|
||||
end
|
||||
|
||||
defp print_failed_threshold(totals, threshold) do
|
||||
Mix.shell().info("Coverage test failed, threshold not met:\n")
|
||||
Mix.shell().info(" Coverage: #{format_number(totals, 6)}%")
|
||||
Mix.shell().info(" Threshold: #{format_number(threshold, 6)}%")
|
||||
Mix.shell().info("")
|
||||
end
|
||||
|
||||
defp display({percentage, name}, threshold) do
|
||||
Mix.shell().info([
|
||||
color(percentage, threshold),
|
||||
@@ -369,6 +377,9 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
defp color(percentage, threshold) when percentage >= threshold, do: :green
|
||||
defp color(_, _), do: :red
|
||||
|
||||
defp format_number(number, length) when is_integer(number),
|
||||
do: format_number(number / 1, length)
|
||||
|
||||
defp format_number(number, length), do: :io_lib.format("~#{length}.2f", [number])
|
||||
|
||||
defp format_name(name) when is_binary(name), do: name
|
||||
|
||||
@@ -479,6 +479,13 @@ defmodule Mix.ReleaseTest do
|
||||
assert message =~
|
||||
"Application :mix has mode :permanent but it depends on :elixir which is set to :none"
|
||||
end
|
||||
|
||||
test "does not raise on child unsafe mode if parent is in `skip_mode_validation_for`" do
|
||||
# `:mix` is a parent that depends on `:elixir`
|
||||
release = release(applications: [elixir: :load], skip_mode_validation_for: [:mix])
|
||||
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start) == :ok
|
||||
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start_clean) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "make_cookie/1" do
|
||||
|
||||
@@ -168,7 +168,14 @@ defmodule Mix.Tasks.TestTest do
|
||||
|
||||
# We skip a test in bar to force coverage below the default threshold
|
||||
# which should result in an exit status of 1.
|
||||
assert {_, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
|
||||
assert {output, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
|
||||
|
||||
assert output =~ """
|
||||
Coverage test failed, threshold not met:
|
||||
|
||||
Coverage: 0.00%
|
||||
Threshold: 90.00%
|
||||
"""
|
||||
|
||||
unless windows?() do
|
||||
assert code == 3
|
||||
|
||||
Reference in New Issue
Block a user