Compare commits

...
25 Commits
Author SHA1 Message Date
José Valim 416bf41a03 Release v1.3.0 2016-06-21 12:25:14 +02:00
José Valim c758c5e401 Update docs for c/1 and c/2 2016-06-21 12:14:04 +02:00
Aleksei Magusev 7b4576f0fc Consider variables are on different lines in assert_receive 2016-06-20 23:34:55 +02:00
José Valim 9192907e68 Do not leak external variables used in guards in assert_receive 2016-06-20 14:45:30 +02:00
José Valim 5533cc4665 Expose schedulers_online and otp_release information 2016-06-16 23:51:55 +02:00
Eric Entin 873dbc16ff more mix xref doc improvements (#4818)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-15 21:44:21 +02:00
José Valim 84c91ef914 Improve mix xref docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-15 21:08:05 +02:00
Eric Entin a977afcd6c mix xref graph (#4811)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-15 20:58:29 +02:00
Aleksei Magusev 8ed30ad926 Do not use deprecated String.strip
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-15 20:58:20 +02:00
Krzysztof Wende 8496d52df5 yecc instead of yeec (#4813)
Typo. should be `yecc` instead of `yeec` in the documentation

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-15 20:58:15 +02:00
José Valim 977bb418a1 Ensure variables in function clauses are not reused, closes #4815 2016-06-15 16:14:37 +02:00
José Valim 735296a4ab Remove elixir_counter in favor of unique_integer 2016-06-15 15:25:04 +02:00
José Valim e34dde79ea Ensure entries are sorted to avoid non-determinism in compilation, closes #4814 2016-06-15 15:13:31 +02:00
José Valim 1eb11374c3 Update CHANGELOG 2016-06-12 23:53:07 +02:00
Eric Entin 9c2ec6a260 mix xref command-style (#4808)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-12 23:52:25 +02:00
Eric Entin 92cf02321b Make sure struct expansion results in a remote dispatch (#4807)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-12 23:52:09 +02:00
Aleksei Magusev 2b5e69aa05 Make sure with/1 does not leak variables to else (#4804)
Conflicts:
	lib/elixir/src/elixir_scope.erl
2016-06-12 21:39:20 +02:00
Aleksei Magusev a56606d5d6 Do not silence unused variable warnings in assert_receive
Closes #4789. It's not needed anymore after e190474955 commit.
2016-06-12 21:39:20 +02:00
José Valim b2329d11a8 Use explicit call for deps() 2016-06-12 18:29:04 +02:00
Felipe Seré 88b3e864d5 Clean deps when deps.compile is run with --force (#4780)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-12 10:00:24 +02:00
Josh Price efaca6c3e9 More informative message for DOT tree generation (#4791)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-12 10:00:19 +02:00
José Valim 67aabe0bac Calculate beam files only when writing manifest data
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-11 15:38:24 +02:00
José Valim 8506cc68bf Ensure conflict with umbrella children are shown, closes #4792
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-11 15:38:24 +02:00
Myron Marston a1bbc1bc20 Document change in behavior of Enum.group_by ordering. (#4794)
On 1.2, here's how `group_by` behaved:

    iex(1)> 1..10 |> Enum.group_by(fn i -> i < 5 end)
    %{false: [10, 9, 8, 7, 6, 5], true: [4, 3, 2, 1]}

On 1.3, here's how it behaves:

    iex(1)> 1..10 |> Enum.group_by(fn i -> i < 5 end)
    %{false: [5, 6, 7, 8, 9, 10], true: [1, 2, 3, 4]}
2016-06-09 20:40:11 +02:00
José Valim 18dfca21f0 Update doc references 2016-06-08 21:14:15 +02:00
42 changed files with 645 additions and 212 deletions
+4 -3
View File
@@ -102,11 +102,11 @@ In any case, the previous behaviour can be brought back with the `--verbose` fla
### mix xref
Speaking about warnings, Mix v1.3 includes a new task called `xref` that performs cross reference checks in your code. One of such checks is the ability to find calls to modules and functions that do not exist. For example, if in your library code you call `ThisModuleDoesNotExist.foo(1, 2, 3)`, `mix xref --unreachable` will be able to find such code and let you know about it.
Speaking about warnings, Mix v1.3 includes a new task called `xref` that performs cross reference checks in your code. One of such checks is the ability to find calls to modules and functions that do not exist. For example, if in your library code you call `ThisModuleDoesNotExist.foo(1, 2, 3)`, `mix xref unreachable` will be able to find such code and let you know about it.
Since such checks can discover possible bugs in your codebase, a new compiler called `xref` has been added to `Mix.compilers/0`, so they run by default every time you compile your code.
We have included other modes in `xref`, such as `mix xref --callers Foo`, to find all places in your code that a function from the module `Foo` is called. We hope other tools and text editors can leverage such features to provide useful functionality for their users.
We have included other modes in `xref`, such as `mix xref callers Foo`, to find all places in your code that a function from the module `Foo` is called. We hope other tools and text editors can leverage such features to provide useful functionality for their users.
### Better dependency tracking
@@ -268,7 +268,7 @@ end
By restricting hierarchies in favor of named setups, it is straight-forward for the developer to glance at each describe block and know exactly the setup steps involved.
## v1.3.0-rc.1 (2016-08-06)
## v1.3.0 (2016-06-21)
### 1. Enhancements
@@ -283,6 +283,7 @@ By restricting hierarchies in favor of named setups, it is straight-forward for
* [CLI] Add `--logger-otp-reports BOOL` and `--logger-sasl-reports BOOL` switches
* [Compiler] Emit a summary of compilation errors when modules are missing
* [Enum] Add `Enum.group_by/3` that allows developers to map on the value being grouped
* [Enum] Make list values in maps returned by `Enum.group_by/2` and `Enum.group_by/3` preserve the order of the input enumerable instead of reversing it.
* [Enum] Add `Enum.drop_every/2` that drops every `nth`, including the first one
* [Exception] Suggest possible functions on `UndefinedFunctionError` for existing modules
* [Exception] Warn if unknown fields are given to `raise/2`
+2 -2
View File
@@ -1,7 +1,7 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
DOCS := master
CANONICAL := master
DOCS := v1.3
CANONICAL := stable
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
+1 -1
View File
@@ -28,7 +28,7 @@ This document simply outlines the release process:
11. Create a new branch "vMAJOR.MINOR"
12. Move docs generation to `docs/vMAJOR.MINOR` in Makefile, set CANONICAL reference and copy them to `docs/stable` (change index.html accordingly)
12. Move docs generation to `docs/vMAJOR.MINOR` in Makefile, set CANONICAL to stable and copy them to `docs/stable` (change index.html accordingly)
13. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
+1 -1
View File
@@ -1 +1 @@
1.3.0-rc.1
1.3.0
+2 -2
View File
@@ -1098,7 +1098,7 @@ defmodule Macro do
case expand do
{:ok, receiver, quoted} ->
next = :elixir_counter.next
next = :erlang.unique_integer()
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
{:ok, _receiver, _name, _args} ->
{original, false}
@@ -1119,7 +1119,7 @@ defmodule Macro do
case expand do
{:ok, receiver, quoted} ->
next = :elixir_counter.next
next = :erlang.unique_integer()
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
:error ->
{original, false}
+1 -1
View File
@@ -499,7 +499,7 @@ defmodule Module do
raise ArgumentError, "expected :file to be given as option"
end
next = :elixir_counter.next
next = :erlang.unique_integer()
line = Keyword.get(opts, :line, 0)
quoted = :elixir_quote.linify_with_context_counter(line, {module, next}, quoted)
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
+25 -3
View File
@@ -34,7 +34,6 @@ defmodule System do
next = System.monotonic_time()
diff = next - prev
Generally speaking, the VM provides three time measurements:
* `os_time/0` - the time reported by the OS. This time may be
@@ -157,8 +156,7 @@ defmodule System do
%{build: build,
date: get_date,
revision: revision,
version: version,
}
version: version}
end
# Returns a string of the build info
@@ -730,6 +728,30 @@ defmodule System do
:os.system_time(normalize_time_unit(unit))
end
@doc """
Returns the OTP release number.
"""
@spec otp_release :: String.t
def otp_release do
:erlang.list_to_binary :erlang.system_info(:otp_release)
end
@doc """
Returns the number of schedulers in the VM.
"""
@spec schedulers :: pos_integer
def schedulers do
:erlang.system_info(:schedulers)
end
@doc """
Returns the number of schedulers online in the VM.
"""
@spec schedulers_online :: pos_integer
def schedulers_online do
:erlang.system_info(:schedulers_online)
end
@doc """
Generates and returns an integer that is unique in the current runtime
instance.
-38
View File
@@ -1,38 +0,0 @@
-module(elixir_counter).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, next/0]).
-behaviour(gen_server).
-define(timeout, 30000). %% 30 seconds
-define(limit, 4294967295). %% 2^32 - 1
next() ->
gen_server:call(?MODULE, next, ?timeout).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
init(Counter) ->
{ok, Counter}.
handle_call(next, _From, Counter) ->
{reply, Counter, bump(Counter)};
handle_call(Request, _From, Counter) ->
{stop, {badcall, Request}, Counter}.
handle_cast(Request, Counter) ->
{stop, {badcast, Request}, Counter}.
handle_info(_Request, Counter) ->
{noreply, Counter}.
terminate(_Reason, _Counter) ->
ok.
code_change(_Old, Counter, _Extra) ->
{ok, Counter}.
bump(Counter) when Counter < ?limit ->
Counter + 1;
bump(_Counter) ->
0.
+1 -1
View File
@@ -218,7 +218,7 @@ is_macro(_) -> false.
unwrap_definitions(File, Module) ->
Table = elixir_module:defs_table(Module),
Entries = ets:match(Table, {{def, '$1'}, '_', '_', '_', '_', '_', '_'}),
{All, Private} = unwrap_definition(Entries, File, Module, Table, [], []),
{All, Private} = unwrap_definition(lists:sort(Entries), File, Module, Table, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
+1 -1
View File
@@ -202,7 +202,7 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
Line = ?line(Meta),
Next = elixir_counter:next(),
Next = erlang:unique_integer(),
try
elixir_exp:expand(
+2 -2
View File
@@ -8,7 +8,7 @@ translate(Meta, Clauses, S) ->
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
{TClause, TS } = elixir_clauses:clause(CMeta, fun translate_fn_match/2,
Args, Expr, Guards, Acc),
{TClause, elixir_scope:mergef(S, TS)}
{TClause, elixir_scope:mergec(S, TS)}
end,
{TClauses, NS} = lists:mapfoldl(Transformer, S, Clauses),
@@ -111,7 +111,7 @@ handle_capture(false, Meta, Expr, E, Sequential) ->
do_capture(Meta, Expr, E, Sequential).
do_capture(Meta, Expr, E, Sequential) ->
case do_escape(Expr, elixir_counter:next(), E, []) of
case do_escape(Expr, erlang:unique_integer(), E, []) of
{_, []} when not Sequential ->
invalid_capture(Meta, Expr, E);
{EExpr, EDict} ->
+1 -1
View File
@@ -19,7 +19,7 @@ expand_struct(Meta, Left, Right, #{context := Context} = E) ->
true when is_atom(ELeft) ->
%% We always record structs when they are expanded
%% as they expect the reference at compile time.
elixir_lexical:record_remote(ELeft, nil, ?m(E, lexical_tracker));
elixir_lexical:record_remote(ELeft, '__struct__', 1, nil, ?line(Meta), ?m(E, lexical_tracker));
true ->
ok;
false when Context == match ->
+3 -12
View File
@@ -2,8 +2,7 @@
-module(elixir_scope).
-export([translate_var/4, build_var/2, context_info/1,
load_binding/2, dump_binding/2,
mergev/2, mergec/2, mergef/2,
merge_vars/2, merge_opt_vars/2,
mergev/2, mergec/2, merge_vars/2, merge_opt_vars/2,
warn_unsafe_var/4, warn_underscored_var_access/3, format_error/1
]).
-include("elixir.hrl").
@@ -128,14 +127,6 @@ mergec(S1, S2) ->
caller=S2#elixir_scope.caller
}.
%% Similar to mergec but does not merge the user vars counter.
mergef(S1, S2) ->
S1#elixir_scope{
super=S2#elixir_scope.super,
caller=S2#elixir_scope.caller
}.
%% Mergers.
merge_vars(V, V) -> V;
@@ -212,8 +203,8 @@ format_error({unused_match, Name, Kind}) ->
format_error({unsafe_var, Name}) ->
io_lib:format("the variable \"~ts\" is unsafe as it has been set inside "
"a case/cond/receive/if/&&/||. Please explicitly return the "
"variable value instead. For example:\n\n"
"a case/cond/receive/if/&&/||. Please explicitly return "
"the variable value instead. For example:\n\n"
" case int do\n"
" 1 -> atom = :one\n"
" 2 -> atom = :two\n"
+2 -12
View File
@@ -15,7 +15,7 @@ init(ok) ->
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_config] % Modules = [Module] | dynamic
},
},
{
elixir_code_server,
@@ -25,17 +25,7 @@ init(ok) ->
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_code_server] % Modules = [Module] | dynamic
},
{
elixir_counter,
{elixir_counter, start_link, []},
permanent, % Restart = permanent | transient | temporary
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
worker, % Type = worker | supervisor
[elixir_counter] % Modules = [Module] | dynamic
}
}
],
{ok, {{one_for_one, 3, 10}, Workers}}.
+24 -19
View File
@@ -65,15 +65,23 @@ expand_else(nil, E) ->
translate(Meta, Args, S) ->
{Parts, [{do, Expr} | ExprList]} = elixir_utils:split_last(Args),
CaseExpr =
case ExprList of
[{else, ElseExpr}] ->
build_else(Meta, build_case(Parts, {ok, Expr}, fun(X) -> {error, X} end), ElseExpr);
[] ->
build_case(Parts, Expr, fun(X) -> X end)
end,
{TC, TS} = elixir_translator:translate(CaseExpr, S#elixir_scope{extra=nil}),
{TC, elixir_scope:mergec(S, TS)}.
case ExprList of
[{else, ElseExpr}] ->
{TCases, TS} = translate_case(Parts, {ok, Expr}, fun(X) -> {error, X} end, S),
translate_else(Meta, TCases, ElseExpr, TS);
[] ->
translate_case(Parts, Expr, fun(X) -> X end, S)
end.
translate_case(Parts, DoExpr, Wrapper, S) ->
Cases = build_case(Parts, DoExpr, Wrapper),
{TCases, TS} = elixir_translator:translate(Cases, S#elixir_scope{extra=nil}),
{TCases, elixir_scope:mergec(S, TS)}.
translate_else(Meta, WithCases, ElseExpr, S) ->
ElseClauses = build_else(Meta, ElseExpr),
{TClauses, TS} = elixir_clauses:clauses(Meta, ElseClauses, S#elixir_scope{extra=nil}),
{{'case', ?ann(Meta), WithCases, TClauses}, elixir_scope:mergec(S, TS)}.
build_case([{'<-', Meta, [{Name, _, Ctx}, _] = Args} | Rest], DoExpr, Wrapper)
when is_atom(Name) andalso is_atom(Ctx) ->
@@ -90,18 +98,15 @@ build_case([Expr | Rest], DoExpr, Wrapper) ->
build_case([], DoExpr, _Wrapper) ->
DoExpr.
build_else(Meta, WithCases, ElseClauses) ->
build_else(Meta, ElseClauses) ->
Result = {result, Meta, ?MODULE},
Clauses = [
{'->', Meta, [[{ok, Result}], Result]}
| else_to_error_clause(ElseClauses)
] ++ [build_raise(Meta)],
{'case', Meta, [WithCases, [{do, Clauses}]]}.
[{match, Meta, [{ok, Result}], Result} |
each_clause_to_error_match(ElseClauses)] ++ [build_raise(Meta)].
else_to_error_clause(Clauses) ->
[{'->', Meta, [[{error, Match}], Expr]} ||
each_clause_to_error_match(Clauses) ->
[{match, Meta, [{error, Match}], Expr} ||
{'->', Meta, [[Match], Expr]} <- Clauses].
build_raise(Meta) ->
Other = {raise, Meta, ?MODULE},
{'->', ?generated, [[{error, Other}], {{'.', Meta, [erlang, error]}, Meta, [{with_clause, Other}]}]}.
Other = {other, Meta, ?MODULE},
{match, ?generated, [{error, Other}], {{'.', Meta, [erlang, error]}, Meta, [{with_clause, Other}]}}.
+12
View File
@@ -18,6 +18,18 @@ defmodule Kernel.FnTest do
refute (fn ^x -> true; _ -> false end).(1.0)
end
test "case function hoisting does not affect anonymous fns" do
assert :undefined =
(if is_a?(:atom, 0) do
user = :defined
else
(fn() ->
user = :undefined
user
end).()
end)
end
test "capture with access" do
assert (&(&1[:hello])).([hello: :world]) == :world
end
@@ -161,6 +161,7 @@ defmodule Kernel.LexicalTrackerTest do
&R.func/0
&Remote.func/0
&Integer.is_even/1
%Macro.Env{}
end
&extract/2
@@ -171,6 +172,8 @@ defmodule Kernel.LexicalTrackerTest do
&is_record/1; def b(a), do: is_record(a)
%Macro.Env{}
Kernel.LexicalTracker.remote_dispatches(__ENV__.module)
end |> elem(3)
""")
@@ -181,14 +184,16 @@ defmodule Kernel.LexicalTrackerTest do
assert {9, Integer, :is_even, 1} in compile_remote_calls
assert {15, Record, :is_record, 1} in compile_remote_calls
assert {18, Integer, :is_even, 1} in compile_remote_calls
assert {21, Record, :extract, 2} in compile_remote_calls
assert {22, Record, :is_record, 1} in compile_remote_calls
assert {23, Remote, :func, 0} in compile_remote_calls
assert {19, Macro.Env, :__struct__, 1} in compile_remote_calls
assert {22, Record, :extract, 2} in compile_remote_calls
assert {23, Record, :is_record, 1} in compile_remote_calls
assert {24, Remote, :func, 0} in compile_remote_calls
assert {25, Integer, :is_even, 1} in compile_remote_calls
assert {27, Kernel, :def, 2} in compile_remote_calls
assert {27, Record, :is_record, 1} in compile_remote_calls
assert {29, Kernel.LexicalTracker, :remote_dispatches, 1} in compile_remote_calls
assert {25, Remote, :func, 0} in compile_remote_calls
assert {26, Integer, :is_even, 1} in compile_remote_calls
assert {28, Kernel, :def, 2} in compile_remote_calls
assert {28, Record, :is_record, 1} in compile_remote_calls
assert {30, Macro.Env, :__struct__, 1} in compile_remote_calls
assert {32, Kernel.LexicalTracker, :remote_dispatches, 1} in compile_remote_calls
runtime_remote_calls = unroll_dispatches(runtime_remote_calls)
assert {7, Record, :extract, 2} in runtime_remote_calls
@@ -200,7 +205,7 @@ defmodule Kernel.LexicalTrackerTest do
assert {16, Remote, :func, 0} in runtime_remote_calls
assert {17, Remote, :func, 0} in runtime_remote_calls
assert {18, :erlang, :==, 2} in runtime_remote_calls
assert {27, :erlang, :is_tuple, 1} in runtime_remote_calls
assert {28, :erlang, :is_tuple, 1} in runtime_remote_calls
end
defp unroll_dispatches(dispatches) do
@@ -48,7 +48,7 @@ defmodule Kernel.MacrosTest do
assert_raise UndefinedFunctionError, fn -> x.value end
end
test "bang do block" do
test "macros with bang and do block have proper precedence" do
import Kernel.MacrosTest.Nested
assert (do_identity! do 1 end) == 1
assert (Kernel.MacrosTest.Nested.do_identity! do 1 end) == 1
@@ -47,6 +47,13 @@ defmodule Kernel.WithTest do
assert result == :error
end
test "does not leak variables to else" do
state = 1
result = with 1 <- state, state = 2, :ok <- error(), do: state, else: (_ -> state)
assert result == 1
assert state == 1
end
test "errors in with" do
assert_raise RuntimeError, fn ->
with({:ok, res} <- oops(), do: res)
+11
View File
@@ -245,6 +245,17 @@ defmodule ModuleTest do
assert ModuleHygiene.test == [1, 2, 3]
end
test "ensure function clauses are ordered" do
{_, _, binary, _} =
defmodule Ordered do
def foo(:foo), do: :bar
def baz(:baz), do: :bat
end
atoms = :beam_lib.chunks(binary, [:atoms])
assert :erlang.phash2(atoms) == 53987778
end
# TODO: Remove this check once we depend only on 19
if :erlang.system_info(:otp_release) >= '19' do
test "create with generated true does not emit warnings" do
contents =
+12
View File
@@ -187,4 +187,16 @@ defmodule SystemTest do
time = System.monotonic_time(:nanoseconds)
assert abs(System.convert_time_unit(time, :nanoseconds, :microseconds)) < abs(time)
end
test "schedulers/0" do
assert System.schedulers >= 1
end
test "schedulers_online/0" do
assert System.schedulers_online >= 1
end
test "otp_release/0" do
assert is_binary System.otp_release
end
end
-1
View File
@@ -15,7 +15,6 @@ extract_interpolations(String) ->
Z
end.
% Interpolations
extract_interpolations_without_interpolation_test() ->
+8 -1
View File
@@ -379,7 +379,6 @@ defmodule ExUnit.Assertions do
<> ExUnit.Assertions.__mailbox__(self()))
end
_ = unquote(vars) # Silence warnings
received
end
end
@@ -428,6 +427,14 @@ defmodule ExUnit.Assertions do
Enum.uniq_by(pins, &elem(&1, 0))
end
defp collect_vars_from_pattern({:when, _, [left, right]}) do
pattern = collect_vars_from_pattern(left)
for {name, _, context} = var <- collect_vars_from_pattern(right),
Enum.any?(pattern, &match?({^name, _, ^context}, &1)),
into: pattern,
do: var
end
defp collect_vars_from_pattern(expr) do
Macro.prewalk(expr, [], fn
{:::, _, [left, _]}, acc ->
@@ -266,6 +266,13 @@ defmodule ExUnit.AssertionsTest do
:world = world
end
test "assert received does not leak external variables used in guards" do
send self(), {:hello, :world}
guard_world = :world
assert_received {:hello, world} when world == guard_world
:world = world
end
test "refute received does not wait" do
false = refute_received :hello
end
+4 -6
View File
@@ -23,8 +23,8 @@ defmodule IEx.Helpers do
There are many other helpers available:
* `b/1` - prints callbacks info and docs for a given module
* `c/1` - compiles a file at the current directory
* `c/2` - compiles a file at the given path
* `c/1` - compiles a file into the current directory
* `c/2` - compiles a file to the given path
* `cd/1` - changes the current directory
* `clear/0` - clears the screen
* `flush/0` - flushes all messages sent to the shell
@@ -115,6 +115,7 @@ defmodule IEx.Helpers do
c "baz.ex"
#=> [Baz]
"""
def c(files, path \\ ".") when is_binary(path) do
files = List.wrap(files)
@@ -123,10 +124,7 @@ defmodule IEx.Helpers do
raise ArgumentError, "expected a binary or a list of binaries as argument"
end
{found, not_found} =
files
|> Enum.map(&Path.expand(&1, path))
|> Enum.partition(&File.exists?/1)
{found, not_found} = Enum.partition(files, &File.exists?/1)
unless Enum.empty?(not_found) do
raise ArgumentError, "could not find files #{Enum.join(not_found, ", ")}"
+7 -6
View File
@@ -177,8 +177,6 @@ defmodule Mix.Compilers.Elixir do
end
defp each_module(pid, cwd, source, module, binary) do
beam = Atom.to_string(module) <> ".beam"
{compile_references, runtime_references} = Kernel.LexicalTracker.remote_references(module)
compile_references =
@@ -214,7 +212,7 @@ defmodule Mix.Compilers.Elixir do
module: module,
kind: kind,
source: source,
beam: beam,
beam: nil, # They are calculated when writing the manifest
binary: binary
)
@@ -265,8 +263,10 @@ defmodule Mix.Compilers.Elixir do
## Resolution
defp update_stale_sources(sources, removed, changed) do
# Remove delete sources
sources =
Enum.reject(sources, fn source(source: source) -> source in removed end)
Enum.reduce(removed, sources, &List.keydelete(&2, &1, source(:source)))
# Store empty sources for the changed ones as the compiler appends data
sources =
Enum.reduce(changed, sources, &List.keystore(&2, &1, source(:source), source(source: &1)))
sources
@@ -394,13 +394,14 @@ defmodule Mix.Compilers.Elixir do
File.mkdir_p!(Path.dirname(manifest))
modules =
for module(beam: beam, binary: binary) = module <- modules do
for module(binary: binary, module: module) = entry <- modules do
beam = Atom.to_string(module) <> ".beam"
if binary do
beam_path = Path.join(compile_path, beam)
File.write!(beam_path, binary)
File.touch!(beam_path, timestamp)
end
module(module, binary: nil)
module(entry, binary: nil, beam: beam)
end
manifest_data =
+11 -4
View File
@@ -56,11 +56,18 @@ defmodule Mix.Tasks.App.Tree do
if opts[:format] == "dot" do
Mix.Utils.write_dot_graph!("app_tree.dot", "application tree",
{:normal, app}, callback, opts)
Mix.shell.info "Generated \"app_tree.dot\" in current directory.\n" <>
"You can use http://www.graphviz.org/ to open it."
[{:normal, app}], callback, opts)
"""
Generated "app_tree.dot" in the current directory. To generate a PNG:
dot -Tpng app_tree.dot -o app_tree.png
For more options see http://www.graphviz.org/.
"""
|> String.trim_trailing
|> Mix.shell.info
else
Mix.Utils.print_tree({:normal, app}, callback, opts)
Mix.Utils.print_tree([{:normal, app}], callback, opts)
end
end
+1 -1
View File
@@ -11,7 +11,7 @@ defmodule Mix.Tasks.Compile do
## Configuration
* `:compilers` - compilers to run, defaults to:
`[:yeec, :leex, :erlang, :elixir, :xref, :app]`
`[:yecc, :leex, :erlang, :elixir, :xref, :app]`
* `:consolidate_protocols` - when `true`, runs protocol
consolidation via the `compile.protocols` task
+1 -1
View File
@@ -36,7 +36,7 @@ defmodule Mix.Tasks.Compile.Xref do
end
defp run_xref do
Mix.Task.run("xref", ["--warnings"])
Mix.Task.run("xref", ["warnings"])
end
defp needs_xref?(opts) do
+11 -6
View File
@@ -95,11 +95,16 @@ defmodule Mix.Tasks.Deps.Check do
defp partition([dep | deps], not_ok, compile) do
cond do
from_umbrella?(dep) -> partition(deps, not_ok, compile)
compilable?(dep) -> partition(deps, not_ok, [dep | compile])
ok?(dep) and local?(dep) -> partition(deps, not_ok, [dep | compile])
ok?(dep) -> partition(deps, not_ok, compile)
true -> partition(deps, [dep | not_ok], compile)
compilable?(dep) ->
if from_umbrella?(dep) do
partition(deps, not_ok, compile)
else
partition(deps, not_ok, [dep | compile])
end
ok?(dep) ->
partition(deps, not_ok, compile)
true ->
partition(deps, [dep | not_ok], compile)
end
end
@@ -123,7 +128,7 @@ defmodule Mix.Tasks.Deps.Check do
defp compilable?(%Mix.Dep{status: {:noappfile, _}}), do: true
defp compilable?(%Mix.Dep{status: {:scmlock, _}}), do: true
defp compilable?(%Mix.Dep{status: :compile}), do: true
defp compilable?(%Mix.Dep{}), do: false
defp compilable?(%Mix.Dep{} = dep), do: ok?(dep) and local?(dep)
defp show_not_ok!([]) do
:ok
+13 -5
View File
@@ -33,7 +33,7 @@ defmodule Mix.Tasks.Deps.Compile do
import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2,
make?: 1, mix?: 1]
@switches [include_children: :boolean]
@switches [include_children: :boolean, force: :boolean]
@spec run(OptionParser.argv) :: :ok
def run(args) do
@@ -44,18 +44,18 @@ defmodule Mix.Tasks.Deps.Compile do
Mix.Project.get!
case OptionParser.parse(args, switches: @switches) do
{_, [], _} ->
{opts, [], _} ->
# Because this command may be invoked explicitly with
# deps.compile, we simply try to compile any available
# dependency.
compile(Enum.filter(loaded(env: Mix.env), &available?/1))
compile(Enum.filter(loaded(env: Mix.env), &available?/1), opts)
{opts, tail, _} ->
compile(loaded_by_name(tail, [env: Mix.env] ++ opts))
compile(loaded_by_name(tail, [env: Mix.env] ++ opts), opts)
end
end
@doc false
def compile(deps) do
def compile(deps, options \\ []) do
shell = Mix.shell
config = Mix.Project.deps_config
@@ -65,6 +65,8 @@ defmodule Mix.Tasks.Deps.Compile do
Enum.map(deps, fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
check_unavailable!(app, status)
clean(app, options)
compiled? = cond do
not is_nil(opts[:compile]) ->
do_compile dep, config
@@ -93,6 +95,12 @@ defmodule Mix.Tasks.Deps.Compile do
if true in compiled, do: Mix.Dep.Lock.touch_manifest, else: :ok
end
defp clean(app, opts) do
if Keyword.get(opts, :force, false) do
File.rm_rf! Path.join [Mix.Project.build_path, "lib", Atom.to_string(app)]
end
end
defp touch_fetchable(scm, path) do
if scm.fetchable? do
File.mkdir_p!(path)
+11 -4
View File
@@ -51,12 +51,19 @@ defmodule Mix.Tasks.Deps.Tree do
if opts[:format] == "dot" do
callback = callback(&format_dot/1, deps, opts)
Mix.Utils.write_dot_graph!("deps_tree.dot", "dependency tree", root, callback, opts)
Mix.shell.info "Generated \"deps_tree.dot\" in current directory.\n" <>
"You can use http://www.graphviz.org/ to open it."
Mix.Utils.write_dot_graph!("deps_tree.dot", "dependency tree", [root], callback, opts)
"""
Generated "deps_tree.dot" in the current directory. To generate a PNG:
dot -Tpng deps_tree.dot -o deps_tree.png
For more options see http://www.graphviz.org/.
"""
|> String.trim_trailing
|> Mix.shell.info
else
callback = callback(&format_tree/1, deps, opts)
Mix.Utils.print_tree(root, callback, opts)
Mix.Utils.print_tree([root], callback, opts)
end
end
+1 -1
View File
@@ -251,7 +251,7 @@ defmodule Mix.Tasks.New do
elixir: "~> <%= @version %>",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps]
deps: deps()]
end
# Configuration for the OTP application
+2 -2
View File
@@ -3,7 +3,7 @@ defmodule Mix.Tasks.Test do
@moduledoc false
def start(compile_path, opts) do
Mix.shell.info "Cover compiling modules ... "
Mix.shell.info "Cover compiling modules ..."
_ = :cover.start
case :cover.compile_beam_directory(compile_path |> to_charlist) do
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Test do
output = opts[:output]
fn() ->
Mix.shell.info "\nGenerating cover results ... "
Mix.shell.info "\nGenerating cover results ..."
File.mkdir_p!(output)
Enum.each :cover.modules, fn(mod) ->
{:ok, _} = :cover.analyse_to_file(mod, '#{output}/#{mod}.html', [:html])
+202 -18
View File
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Xref do
use Mix.Task
alias Mix.Tasks.Compile.Elixir, as: E
import Mix.Compilers.Elixir, only: [read_manifest: 2, source: 1, source: 2]
import Mix.Compilers.Elixir, only: [read_manifest: 2, source: 1, source: 2, module: 1]
@shortdoc "Performs cross reference checks"
@recursive true
@@ -12,23 +12,82 @@ defmodule Mix.Tasks.Xref do
## Xref modes
The following options control the information xref can emit.
The `xref` task expects a mode as first argument:
mix xref MODE
All available modes are discussed below.
### warnings
* `--warnings` - prints warnings for violated cross reference checks
* `--unreachable` - prints all unreachable "file:line: module.function/arity" entries
* `--callers` - prints all references of given `Module`, `Module.function`, or
`Module.function/arity`
Prints warnings for violated cross reference checks:
mix xref warnings
This is the mode used by Mix during compilation.
## Command line options
### unreachable
Prints all unreachable "file:line: module.function/arity" entries:
mix xref unreachable
The "file:line" represents the file and line a call to an unknown
"module.function/arity" is made.
### callers CALLEE
Prints all callers of the given `CALLEE`, which can be one of: `Module`,
`Module.function`, or `Module.function/arity`. Examples:
mix xref callers MyMod
mix xref callers MyMod.fun
mix xref callers MyMod.fun/3
### graph
Prints a file dependency graph where an edge from `A` to `B` indicates
that `A` depends on `B`.
mix xref graph --format dot
The following options are accepted:
* `--exclude` - paths to exclude
* `--source` - display all files that the given source file references (directly or indirectly)
* `--sink` - display all files that reference the given file (directly or indirectly)
* `--format` - can be set to one of:
* `pretty` - use Unicode codepoints for formatting the graph.
This is the default except on Windows
* `plain` - do not use Unicode codepoints for formatting the graph.
This is the default on Windows
* `dot` - produces a DOT graph description in `xref_graph.dot` in the
current directory. Warning: this will override any previously generated file
The `--source` and `--sink` options are particularly useful when trying to understand how
the modules in a particular file interact with the whole system.
## Shared options
Those options are shared across all modes:
* `--no-compile` - do not compile even if files require compilation
* `--no-deps-check` - do not check dependencies
* `--no-archives-check` - do not check archives
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
## Configuration
All configuration for Xref should be placed under the key `:xref`.
All configuration for Xref should be placed under the key `:xref`.
* `:exclude` - a list of modules and `{module, function, arity}` tuples to ignore when checking
cross references. For example: `[MissingModule, {MissingModule2, :missing_func, 2}]`
@@ -36,31 +95,32 @@ defmodule Mix.Tasks.Xref do
"""
@switches [compile: :boolean, deps_check: :boolean, archives_check: :boolean,
warnings: :boolean, unreachable: :boolean, elixir_version_check: :boolean,
callers: :string]
elixir_version_check: :boolean, exclude: :keep, format: :string,
source: :string, sink: :string]
@doc """
Runs this task.
"""
@spec run(OptionParser.argv) :: :ok | :error
def run(args) do
{opts, _} =
{opts, args} =
OptionParser.parse!(args, strict: @switches)
if Keyword.get(opts, :compile, true) do
Mix.Task.run("compile")
end
modes = [:warnings, :unreachable, :callers]
case Keyword.take(opts, modes) do
[warnings: true] ->
case args do
["warnings"] ->
warnings()
[unreachable: true] ->
["unreachable"] ->
unreachable()
[callers: callee] ->
["callers", callee] ->
callers(callee)
["graph"] ->
graph(opts)
_ ->
Mix.raise "xref expects exactly one of the following modes: --warnings, --unreachable, --callers"
Mix.raise "xref doesn't support this command, see mix help xref for more information"
end
end
@@ -90,6 +150,12 @@ defmodule Mix.Tasks.Xref do
:ok
end
defp graph(opts) do
write_graph(file_references(), excluded(opts), opts)
:ok
end
## Unreachable
defp unreachable(pair_fun) do
@@ -279,12 +345,130 @@ defmodule Mix.Tasks.Xref do
defp raise_invalid_callee(callee) do
message =
"xref --callers expects Module, Module.function, or Module.function/arity, got: " <>
"xref callers CALLEE expects Module, Module.function, or Module.function/arity, got: " <>
callee
Mix.raise message
end
## Graph helpers
defp excluded(opts) do
Keyword.get_values(opts, :exclude)
|> Enum.flat_map(&[{&1, nil}, {&1, "(compile)"}, {&1, "(runtime)"}])
end
defp file_references() do
module_sources =
for manifest <- E.manifests(),
manifest_data = read_manifest(manifest, ""),
module(module: module, source: source) <- manifest_data,
source = Enum.find(manifest_data, &match?(source(source: ^source), &1)),
do: {module, source},
into: %{}
all_modules = MapSet.new(module_sources, &elem(&1, 0))
Map.new module_sources, fn {module, source} ->
source(runtime_references: runtime, compile_references: compile, source: file) = source
compile_references =
compile
|> MapSet.new()
|> MapSet.delete(module)
|> MapSet.intersection(all_modules)
|> Enum.filter(&module_sources[&1] != source)
|> Enum.map(&{source(module_sources[&1], :source), "(compile)"})
runtime_references =
runtime
|> MapSet.new()
|> MapSet.delete(module)
|> MapSet.intersection(all_modules)
|> Enum.filter(&module_sources[&1] != source)
|> Enum.map(&{source(module_sources[&1], :source), nil})
{file, compile_references ++ runtime_references}
end
end
defp write_graph(file_references, excluded, opts) do
{root, file_references} =
case {opts[:source], opts[:sink]} do
{nil, nil} ->
{Enum.map(file_references, &{elem(&1, 0), nil}) -- excluded, file_references}
{source, nil} ->
if file_references[source] do
{[{source, nil}], file_references}
else
Mix.raise "Source could not be found: #{source}"
end
{nil, sink} ->
if file_references[sink] do
file_references = filter_for_sink(file_references, sink)
roots =
file_references
|> Map.delete(sink)
|> Enum.map(&{elem(&1, 0), nil})
{roots -- excluded, file_references}
else
Mix.raise "Sink could not be found: #{sink}"
end
{_, _} ->
Mix.raise "mix xref graph expects only one of --source and --sink"
end
callback =
fn {file, type} ->
children = Map.get(file_references, file, [])
{{file, type}, children -- excluded}
end
if opts[:format] == "dot" do
Mix.Utils.write_dot_graph!("xref_graph.dot", "xref graph",
root, callback, opts)
"""
Generated "xref_graph.dot" in the current directory. To generate a PNG:
dot -Tpng xref_graph.dot -o xref_graph.png
For more options see http://www.graphviz.org/.
"""
|> String.trim_trailing()
|> Mix.shell.info()
else
Mix.Utils.print_tree(root, callback, opts)
end
end
defp filter_for_sink(file_references, sink) do
file_references
|> invert_references()
|> do_filter_for_sink([{sink, nil}], %{})
|> invert_references()
end
defp do_filter_for_sink(file_references, new_nodes, acc) do
Enum.reduce new_nodes, acc, fn {new_node_name, _type}, acc ->
new_nodes = file_references[new_node_name]
if acc[new_node_name] || !new_nodes do
acc
else
do_filter_for_sink(file_references, new_nodes, Map.put(acc, new_node_name, new_nodes))
end
end
end
defp invert_references(file_references) do
Enum.reduce file_references, %{}, fn {file, references}, acc ->
Enum.reduce references, acc, fn {reference, type}, acc ->
Map.update(acc, reference, [{file, type}], &[{file, type} | &1])
end
end
end
## Helpers
defp each_source_entries(entries_fun, pair_fun) do
+48 -34
View File
@@ -142,31 +142,40 @@ defmodule Mix.Utils do
end) |> Enum.uniq
end
@type tree_node :: {name :: String.Chars.t, edge_info :: String.Chars.t}
@doc """
Prints the given tree according to the callback.
The callback will be invoked for each node and it
must either return `{printed, children}` tuple or
`false` if the given node must not be printed.
must return a `{printed, children}` tuple.
"""
@spec print_tree(term, (term -> {String.t, [term]}), Keyword.t) :: :ok
def print_tree(root, callback, opts \\ []) do
@spec print_tree([tree_node], (tree_node -> {tree_node, [tree_node]}), Keyword.t) :: :ok
def print_tree(nodes, callback, opts \\ []) do
pretty =
case Keyword.get(opts, :format) do
"pretty" -> true
"plain" -> false
_ -> elem(:os.type, 0) != :win32
end
print_tree([root], [], pretty, callback)
print_tree(nodes, [], nil, MapSet.new(), pretty, callback)
:ok
end
defp print_tree([], _depth, _pretty, _callback), do: :ok
defp print_tree([node | nodes], depth, pretty, callback) do
{{name, info}, children} = callback.(node)
space = if info, do: " ", else: ""
Mix.shell.info("#{depth(pretty, depth)}#{prefix(pretty, depth, nodes)}#{name}#{space}#{info}")
print_tree(children, [(nodes != []) | depth], pretty, callback)
print_tree(nodes, depth, pretty, callback)
defp print_tree([], _depth, _parent, seen, _pretty, _callback), do: seen
defp print_tree([node | nodes], depth, parent, seen, pretty, callback) do
{{name, info}, children} = callback.(node)
key = {parent, name}
if MapSet.member?(seen, key) do
seen
else
space = if info, do: " ", else: ""
Mix.shell.info("#{depth(pretty, depth)}#{prefix(pretty, depth, nodes)}#{name}#{space}#{info}")
seen = print_tree(children, [(nodes != []) | depth], name, MapSet.put(seen, key), pretty, callback)
print_tree(nodes, depth, parent, seen, pretty, callback)
end
end
defp depth(_pretty, []), do: ""
@@ -188,37 +197,42 @@ defmodule Mix.Utils do
Outputs the given tree according to the callback as a DOT graph.
The callback will be invoked for each node and it
must either return `{printed, children}` tuple or
`false` if the given node must not be printed.
must return a `{printed, children}` tuple.
"""
@spec write_dot_graph!(Path.t, String.t, term, (term -> {String.t, [term]}), Keyword.t) :: :ok
def write_dot_graph!(path, title, root, callback, _opts \\ []) do
{{parent, _}, children} = callback.(root)
{dot, _} = build_dot_graph(parent, children, %{}, callback)
@spec write_dot_graph!(Path.t, String.t, [tree_node], (tree_node -> {tree_node, [tree_node]}), Keyword.t) :: :ok
def write_dot_graph!(path, title, nodes, callback, _opts \\ []) do
{dot, _} = build_dot_graph(make_ref(), nodes, MapSet.new(), callback)
File.write! path, "digraph \"#{title}\" {\n#{dot}}\n"
end
defp build_dot_graph(_parent, [], seen, _callback), do: {"", seen}
defp build_dot_graph(parent, [node | nodes], seen, callback) do
{{name, edge_info}, children} = callback.(node)
{current, seen} = build_dot_current(parent, name, edge_info, seen)
{children, seen} = build_dot_graph(name, children, seen, callback)
{siblings, seen} = build_dot_graph(parent, nodes, seen, callback)
{current <> children <> siblings, seen}
key = {parent, name}
if MapSet.member?(seen, key) do
{"", seen}
else
seen = MapSet.put(seen, key)
current = build_dot_current(parent, name, edge_info)
{children, seen} = build_dot_graph(name, children, seen, callback)
{siblings, seen} = build_dot_graph(parent, nodes, seen, callback)
{current <> children <> siblings, seen}
end
end
defp build_dot_current(parent, name, edge_info, seen) do
key = {parent, name}
case seen do
%{^key => _} ->
{"", seen}
%{} when is_nil(edge_info) ->
{~s( "#{parent}" -> "#{name}"\n),
Map.put(seen, key, true),}
%{} ->
{~s( "#{parent}" -> "#{name}" [label=\"#{edge_info}\"]\n),
Map.put(seen, key, true)}
end
defp build_dot_current(parent, name, edge_info) do
edge_info =
if edge_info do
~s( [label="#{edge_info}"])
end
parent =
unless is_reference(parent) do
~s("#{parent}" -> )
end
~s( #{parent}"#{name}"#{edge_info}\n)
end
@doc false
+1
View File
@@ -87,6 +87,7 @@ defmodule Mix.Tasks.App.TreeTest do
assert File.read!("app_tree.dot") == """
digraph "application tree" {
"test"
"test" -> "elixir"
"test" -> "logger"
"logger" -> "elixir"
@@ -117,6 +117,7 @@ defmodule Mix.Tasks.Deps.TreeTest do
assert File.read!("deps_tree.dot") == """
digraph "dependency tree" {
"sample"
"sample" -> "git_repo" [label=">= 0.1.0"]
"sample" -> "deps_on_git_repo" [label="0.2.0"]
}
@@ -127,6 +128,7 @@ defmodule Mix.Tasks.Deps.TreeTest do
assert File.read!("deps_tree.dot") == """
digraph "dependency tree" {
"sample"
"sample" -> "git_repo" [label=">= 0.1.0"]
"sample" -> "deps_on_git_repo" [label="0.2.0"]
"deps_on_git_repo" -> "git_repo" [label=""]
+14
View File
@@ -193,6 +193,20 @@ defmodule Mix.Tasks.DepsTest do
## deps.unlock
test "cleans and recompiles artifacts if --force given" do
Mix.Project.push SuccessfulDepsApp
in_fixture "deps_status", fn ->
Mix.Tasks.Deps.Compile.run []
File.touch! "_build/dev/lib/ok/clean-me"
Mix.Tasks.Deps.Compile.run ["--force"]
refute File.exists? "_build/dev/lib/ok/clean-me"
end
end
## deps.unlock
test "unlocks all deps", context do
Mix.Project.push DepsApp
in_tmp context.test, fn ->
+148 -12
View File
@@ -241,7 +241,7 @@ defmodule Mix.Tasks.XrefTest do
File.write!("lib/a.ex", contents)
assert capture_io(:stderr, fn ->
assert Mix.Task.run("xref", ["--warnings"]) == :error
assert Mix.Task.run("xref", ["warnings"]) == :error
end) == expected
end
end
@@ -251,7 +251,7 @@ defmodule Mix.Tasks.XrefTest do
File.write!("lib/a.ex", contents)
assert capture_io(:stderr, fn ->
assert Mix.Task.run("xref", ["--warnings"]) == :ok
assert Mix.Task.run("xref", ["warnings"]) == :ok
end) == ""
end
end
@@ -274,7 +274,7 @@ defmodule Mix.Tasks.XrefTest do
File.write!("lib/a.ex", contents)
assert capture_io(fn ->
assert Mix.Task.run("xref", ["--unreachable"]) == :error
assert Mix.Task.run("xref", ["unreachable"]) == :error
end) == expected
end
end
@@ -420,12 +420,10 @@ defmodule Mix.Tasks.XrefTest do
test "callers: no argument gives error" do
in_fixture "no_mixfile", fn ->
message =
"Could not invoke task \"xref\": 1 error found!\n" <>
"--callers : Missing argument of type string"
message = "xref doesn't support this command, see mix help xref for more information"
assert_raise Mix.Error, message, fn ->
assert Mix.Task.run("xref", ["--callers"]) == :error
assert Mix.Task.run("xref", ["callers"]) == :error
end
end
end
@@ -433,10 +431,10 @@ defmodule Mix.Tasks.XrefTest do
test "callers: gives nice error for quotable but invalid callers spec" do
in_fixture "no_mixfile", fn ->
message =
"xref --callers expects Module, Module.function, or Module.function/arity, got: Module.func(arg)"
"xref callers CALLEE expects Module, Module.function, or Module.function/arity, got: Module.func(arg)"
assert_raise Mix.Error, message, fn ->
Mix.Task.run("xref", ["--callers", "Module.func(arg)"])
Mix.Task.run("xref", ["callers", "Module.func(arg)"])
end
end
end
@@ -444,10 +442,10 @@ defmodule Mix.Tasks.XrefTest do
test "callers: gives nice error for unquotable callers spec" do
in_fixture "no_mixfile", fn ->
message =
"xref --callers expects Module, Module.function, or Module.function/arity, got: %"
"xref callers CALLEE expects Module, Module.function, or Module.function/arity, got: %"
assert_raise Mix.Error, message, fn ->
Mix.Task.run("xref", ["--callers", "%"])
Mix.Task.run("xref", ["callers", "%"])
end
end
end
@@ -458,8 +456,146 @@ defmodule Mix.Tasks.XrefTest do
File.write!("lib/b.ex", contents_b)
assert capture_io(fn ->
assert Mix.Task.run("xref", ["--callers", callee]) == :ok
assert Mix.Task.run("xref", ["callers", callee]) == :ok
end) == expected
end
end
## Graph
test "graph: basic usage" do
assert_graph """
lib/a.ex
└── lib/b.ex
└── lib/a.ex
lib/b.ex
lib/c.ex
lib/d.ex
└── lib/a.ex (compile)
"""
end
test "graph: exclude" do
assert_graph ~w[--exclude lib/c.ex --exclude lib/b.ex], """
lib/a.ex
lib/d.ex
└── lib/a.ex (compile)
"""
end
test "graph: exclude 1" do
assert_graph ~w[--exclude lib/d.ex], """
lib/a.ex
└── lib/b.ex
└── lib/a.ex
lib/b.ex
lib/c.ex
"""
end
test "graph: dot format" do
assert_graph ~w[--format dot], true, """
digraph "xref graph" {
"lib/a.ex"
"lib/a.ex" -> "lib/b.ex"
"lib/b.ex" -> "lib/a.ex"
"lib/b.ex"
"lib/c.ex"
"lib/d.ex"
"lib/d.ex" -> "lib/a.ex" [label="(compile)"]
}
"""
end
test "graph: source" do
assert_graph ~w[--source lib/a.ex], """
lib/a.ex
└── lib/b.ex
└── lib/a.ex
"""
end
test "graph: invalid source" do
assert_raise Mix.Error, "Source could not be found: lib/a2.ex", fn ->
assert_graph ~w[--source lib/a2.ex], ""
end
end
test "graph: sink" do
assert_graph ~w[--sink lib/b.ex], """
lib/a.ex
└── lib/b.ex
└── lib/a.ex
lib/d.ex
└── lib/a.ex (compile)
"""
end
test "graph: invalid sink" do
assert_raise Mix.Error, "Sink could not be found: lib/b2.ex", fn ->
assert_graph ~w[--sink lib/b2.ex], ""
end
end
test "graph: sink and source is error" do
assert_raise Mix.Error, "mix xref graph expects only one of --source and --sink", fn ->
assert_graph ~w[--source lib/a.ex --sink lib/b.ex], ""
end
end
defp assert_graph(opts \\ [], dot \\ false, expected) do
in_fixture "no_mixfile", fn ->
File.write! "lib/a.ex", """
defmodule A do
def a do
B.a
end
def b, do: :ok
end
"""
File.write! "lib/b.ex", """
defmodule B do
def a do
A.a
B.a
end
end
"""
File.write! "lib/c.ex", """
defmodule C do
end
"""
File.write! "lib/d.ex", """
defmodule :d do
A.b
end
"""
assert Mix.Task.run("xref", opts ++ ["graph"]) == :ok
result =
if dot do
File.read!("xref_graph.dot")
else
assert "Compiling 4 files (.ex)\nGenerated sample app\n" <> result =
receive_until_no_messages([])
result
end
assert result == expected
end
end
defp receive_until_no_messages(acc) do
receive do
{:mix_shell, :info, [line]} -> receive_until_no_messages([acc, line | "\n"])
after
0 -> IO.iodata_to_binary(acc)
end
end
end
+26 -1
View File
@@ -135,7 +135,7 @@ defmodule Mix.UmbrellaTest do
Mix.env(:test)
end
test "loads umbrella child dependencies in umbrellas" do
test "loads umbrella sibling dependencies with :in_umbrella" do
in_fixture "umbrella_dep/deps/umbrella", fn ->
Mix.Project.in_project :umbrella, ".", fn _ ->
File.write! "apps/bar/mix.exs", """
@@ -157,6 +157,31 @@ defmodule Mix.UmbrellaTest do
end
end
test "finds umbrella sibling dependencies conflicts with :in_umbrella" do
in_fixture "umbrella_dep/deps/umbrella", fn ->
Mix.Project.in_project :umbrella, ".", fn _ ->
File.write! "apps/bar/mix.exs", """
defmodule Bar.Mixfile do
use Mix.Project
def project do
[app: :bar,
version: "0.1.0",
deps: [{:foo, in_umbrella: true, env: :unknown}]]
end
end
"""
assert_raise Mix.Error, fn ->
Mix.Tasks.Deps.Get.run []
end
assert_received {:mix_shell, :error, ["Dependencies have diverged:"]}
assert_received {:mix_shell, :error, [" the dependency foo in mix.exs is overriding a child dependency" <> _]}
end
end
end
## Umbrellas as a dependency
test "list deps for umbrella as dependency" do
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.3.0-rc.1"},
{vsn, "1.3.0"},
{modules, [
elixir
]},