Compare commits
74
Commits
v1.8.0-rc.0
...
v1.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56f1037973 | ||
|
|
e5fcef3832 | ||
|
|
9081a7aad2 | ||
|
|
328f581ef5 | ||
|
|
aa695875fe | ||
|
|
aa039ce9f9 | ||
|
|
2fdf96e747 | ||
|
|
494bded12a | ||
|
|
2b0fc529ee | ||
|
|
aaa775a3bf | ||
|
|
6647b00fcf | ||
|
|
d149c4c1e0 | ||
|
|
58810bd33c | ||
|
|
050cb45664 | ||
|
|
7f59aeeb6f | ||
|
|
e2abd35b3c | ||
|
|
cc35c8fc22 | ||
|
|
2718631eb6 | ||
|
|
9d9aa8bff8 | ||
|
|
65aee00809 | ||
|
|
9f028834d7 | ||
|
|
44ef53ec2b | ||
|
|
d1290d62db | ||
|
|
301aa6eddc | ||
|
|
098b2d7cfd | ||
|
|
d92b7a331b | ||
|
|
8930ea7598 | ||
|
|
01a1acd27b | ||
|
|
127691d4ad | ||
|
|
e848e87f52 | ||
|
|
bcadd96e31 | ||
|
|
99a0bfa074 | ||
|
|
5d00232633 | ||
|
|
c319bc450e | ||
|
|
a9ed654ae2 | ||
|
|
6dacf53268 | ||
|
|
5e22b12272 | ||
|
|
d4501893c7 | ||
|
|
744859ee42 | ||
|
|
4596baab3f | ||
|
|
c065041263 | ||
|
|
95bff23ede | ||
|
|
0bc04195f2 | ||
|
|
d195b75aa3 | ||
|
|
dc06a18287 | ||
|
|
54c0574be2 | ||
|
|
8d50e1fa95 | ||
|
|
e70d9723eb | ||
|
|
3ce029615d | ||
|
|
416bf41a03 | ||
|
|
c758c5e401 | ||
|
|
7b4576f0fc | ||
|
|
9192907e68 | ||
|
|
5533cc4665 | ||
|
|
873dbc16ff | ||
|
|
84c91ef914 | ||
|
|
a977afcd6c | ||
|
|
8ed30ad926 | ||
|
|
8496d52df5 | ||
|
|
977bb418a1 | ||
|
|
735296a4ab | ||
|
|
e34dde79ea | ||
|
|
1eb11374c3 | ||
|
|
9c2ec6a260 | ||
|
|
92cf02321b | ||
|
|
2b5e69aa05 | ||
|
|
a56606d5d6 | ||
|
|
b2329d11a8 | ||
|
|
88b3e864d5 | ||
|
|
efaca6c3e9 | ||
|
|
67aabe0bac | ||
|
|
8506cc68bf | ||
|
|
a1bbc1bc20 | ||
|
|
18dfca21f0 |
+65
-3
@@ -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,68 @@ 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.2 (2016-07-15)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Support guards in `else` clauses in `with`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Add `MIX_NO_DEPS` env var for disabling dep loading. Used for third-party scripts and tools like Nix package manager
|
||||
* [Mix] Add `mix test --listen-on-stdin` that automatically reruns tests on stdin
|
||||
* [Mix] Disable `--warnings-as-errors` when compiling dependencies
|
||||
* [Mix] Add `--filter` option to `mix deps.unlock` to unlock only matching dependencies
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] Return `nil` if enumerable halts in `Enum.find_index/3`
|
||||
* [Kernel] Do not attempt to load modules that have not been required when invoking regular functions, otherwise this invalidates the `@compile {:autoload, false}` directive.
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Ensure missing protocol dependencies are discarded in umbrella projects with shared build
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Diff] Ensure no leading or trailing when diffing some maps
|
||||
|
||||
## v1.3.1 (2016-06-28)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] `Add import_file_if_available` for importing files only if they are available
|
||||
* [IEx.Helpers] `Add import_if_available` for importing modules only if they are available
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Ensure structs can be expanded in dynamic module names
|
||||
* [Kernel] Ensure aliases warnings are not accidentally discarded when the same module is imported
|
||||
* [Kernel.ParallelCompiler] Ensure two modules with cyclic struct dependencies cannot run into a deadlock when compiling
|
||||
* [Kernel.Typespec] Support module attributes in remote types
|
||||
* [Module] Do not expect stacktraces to be always present when dispatching to locals during the module compilation
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Fix `h` helper for operators
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Do not load modules for xref purposes, instead use BEAM info
|
||||
* [Mix] Ensure `deps.check` does not check archives (that's done in loadpaths)
|
||||
* [Mix] Validate application properties before traversing them
|
||||
* [Mix] Check for proper Makefile when compiling on Windows
|
||||
* [Mix] Enforce space after comma in `mix do`
|
||||
|
||||
## v1.3.0 (2016-06-21)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -283,6 +344,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`
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
|
||||
@@ -435,6 +435,13 @@ defmodule Access do
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
||||
|
||||
`at/1` can also be used to pop elements out of a list or
|
||||
a key inside of a list:
|
||||
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> pop_in(list, [Access.at(0)])
|
||||
{%{name: "john"}, [%{name: "mary"}]}
|
||||
iex> pop_in(list, [Access.at(0), :name])
|
||||
{"john", [%{}, %{name: "mary"}]}
|
||||
|
||||
@@ -477,7 +484,7 @@ defmodule Access do
|
||||
defp get_and_update_at([head | rest], 0, next, updates) do
|
||||
case next.(head) do
|
||||
{get, update} -> {get, :lists.reverse([update | updates], rest)}
|
||||
:pop -> {head, :lists.reverse([head | updates], rest)}
|
||||
:pop -> {head, :lists.reverse(updates, rest)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -861,13 +861,13 @@ defmodule Enum do
|
||||
|
||||
def find_index(enumerable, fun) do
|
||||
res =
|
||||
Enumerable.reduce(enumerable, {:cont, 0}, fn(entry, acc) ->
|
||||
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
|
||||
Enumerable.reduce(enumerable, {:cont, {:not_found, 0}}, fn(entry, {status, index}) ->
|
||||
if fun.(entry), do: {:halt, {:found, index}}, else: {:cont, {:not_found, index + 1}}
|
||||
end)
|
||||
|
||||
case res do
|
||||
{:halted, entry} -> entry
|
||||
{:done, _} -> nil
|
||||
{_, {:found, index}} -> index
|
||||
{_, {:not_found, _}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -997,7 +997,8 @@ defmodule Enum do
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
def group_by(enumerable, dict, fun) when is_function(fun, 1) do
|
||||
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated, please use a map instead"
|
||||
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
|
||||
"A map is used by default and it is no longer required to pass one to this function"
|
||||
reduce(reverse(enumerable), dict, fn(entry, categories) ->
|
||||
Dict.update(categories, fun.(entry), [entry], &[entry | &1])
|
||||
end)
|
||||
|
||||
@@ -15,7 +15,11 @@ defmodule Exception do
|
||||
"""
|
||||
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
@type t :: %{
|
||||
required(:__struct__) => module,
|
||||
required(:__exception__) => true,
|
||||
atom => any
|
||||
}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
||||
|
||||
@@ -746,7 +746,7 @@ defmodule GenServer do
|
||||
@spec reply(from, term) :: :ok
|
||||
def reply(client, reply)
|
||||
|
||||
def reply({to, tag}, reply) do
|
||||
def reply({to, tag}, reply) when is_pid(to) do
|
||||
try do
|
||||
send(to, {tag, reply})
|
||||
:ok
|
||||
|
||||
@@ -1862,7 +1862,7 @@ defmodule Kernel do
|
||||
In case any entry returns `nil`, its key will be removed
|
||||
and the deletion will be considered a success.
|
||||
"""
|
||||
@spec pop_in(Access.t, nonempty_list(term)) :: Access.t
|
||||
@spec pop_in(Access.t, nonempty_list(term)) :: {term, Access.t}
|
||||
def pop_in(data, keys)
|
||||
def pop_in(nil, [h | _]), do: Access.pop(nil, h)
|
||||
def pop_in(data, keys), do: do_pop_in(data, keys)
|
||||
@@ -3473,7 +3473,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
unquote(builder)
|
||||
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
fields
|
||||
end
|
||||
end
|
||||
@@ -4314,7 +4314,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
# TODO: Deprecate by v1.5
|
||||
# @doc false
|
||||
@doc false
|
||||
defmacro to_char_list(arg) do
|
||||
quote do: Kernel.to_charlist(unquote(arg))
|
||||
end
|
||||
|
||||
@@ -148,17 +148,15 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
def handle_cast({:add_import, module, fas, line, warn}, state) when is_atom(module) do
|
||||
directives =
|
||||
for {{:import, {import_module, _, _}}, _} = directive <- state.directives,
|
||||
module != import_module,
|
||||
do: directive,
|
||||
into: %{}
|
||||
|
||||
directives = add_directive(directives, module, line, warn, :import)
|
||||
state.directives
|
||||
|> Enum.reject(&match?({{:import, {^module, _, _}}, _}, &1))
|
||||
|> :maps.from_list
|
||||
|> add_directive(module, line, warn, :import)
|
||||
|
||||
directives =
|
||||
Enum.reduce fas, directives, fn {function, arity}, directives ->
|
||||
Enum.reduce(fas, directives, fn {function, arity}, directives ->
|
||||
add_directive(directives, {module, function, arity}, line, warn, :import)
|
||||
end
|
||||
end)
|
||||
|
||||
{:noreply, %{state | directives: directives}}
|
||||
end
|
||||
|
||||
@@ -448,7 +448,7 @@ defmodule Kernel.Typespec do
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) when is_atom(args),
|
||||
do: translate_spec(kind, meta, name, [], return, guard, caller)
|
||||
defp translate_spec(:macrocallback, meta, name, args, return, guard, caller),
|
||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t) | args], return, guard, caller)
|
||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", macro_args(args), return, guard, caller)
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) do
|
||||
ensure_no_defaults!(args)
|
||||
|
||||
@@ -470,6 +470,10 @@ defmodule Kernel.Typespec do
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp macro_args(args) do
|
||||
[quote(do: {line :: Macro.Env.line, env :: Macro.Env.t}) | args]
|
||||
end
|
||||
|
||||
defp ensure_no_defaults!(args) do
|
||||
:lists.foreach fn
|
||||
{:::, _, [left, right]} ->
|
||||
@@ -864,6 +868,19 @@ defmodule Kernel.Typespec do
|
||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||
end
|
||||
|
||||
# Handle remote calls in the form of @module_attribute.type.
|
||||
# These are not handled by the general remote type clause as calling
|
||||
# Macro.expand/2 on the remote does not expand module attributes (but expands
|
||||
# things like __MODULE__).
|
||||
defp typespec({{:., meta, [{:@, _, [{attr, _, _}]}, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Module.get_attribute(caller.module, attr)
|
||||
unless is_atom(remote) and remote != nil do
|
||||
message = "invalid remote in typespec: #{Macro.to_string(orig)} (@#{attr} is #{inspect remote})"
|
||||
compile_error(caller, message)
|
||||
end
|
||||
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
|
||||
@@ -84,4 +84,11 @@ defmodule Kernel.Utils do
|
||||
List.wrap(Module.get_attribute(module, :enforce_keys)),
|
||||
Module.get_attribute(module, :derive)}
|
||||
end
|
||||
|
||||
def announce_struct(module) do
|
||||
case :erlang.get(:elixir_compiler_pid) do
|
||||
:undefined -> :ok
|
||||
pid -> send(pid, {:struct_available, module})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -369,7 +369,7 @@ defmodule Module do
|
||||
* `@compile :debug_info` - includes `:debug_info` regardless of the
|
||||
setting in `Code.compiler_options`
|
||||
|
||||
* `@compile {:debug_info, false} - disables `:debug_info` regardless
|
||||
* `@compile {:debug_info, false}` - disables `:debug_info` regardless
|
||||
of the setting in `Code.compiler_options`
|
||||
|
||||
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
||||
@@ -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))
|
||||
|
||||
@@ -15,13 +15,24 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
It returns a three-element tuple as follows:
|
||||
It returns a three-element tuple with the form `{parsed, args, invalid}`, where:
|
||||
|
||||
1. parsed switches,
|
||||
2. remaining arguments,
|
||||
3. invalid options.
|
||||
* `parsed` is a keyword list of parsed switches with `{switch_name, value}`
|
||||
tuples in it; `switch_name` is the atom representing the switch name while
|
||||
`value` is the value for that switch parsed according to `opts` (see the
|
||||
"Examples" section for more information)
|
||||
* `args` is a list of the remaining arguments in `argv` as strings
|
||||
* `invalid` is a list of invalid options as `{option_name, value}` where
|
||||
`option_name` is the raw option and `value` is `nil` if the option wasn't
|
||||
expected or the string value if the value didn't have the expected type for
|
||||
the corresponding option
|
||||
|
||||
## Examples
|
||||
Elixir converts switches to underscored atoms, so `--source-path` becomes
|
||||
`:source_path`. This is done to better suit Elixir conventions. However, this
|
||||
means that switches can't contain underscores and switches that do contain
|
||||
underscores are always returned in the list of invalid options.
|
||||
|
||||
Without any options, this function will try to parse all switches in the `argv`.
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{[debug: true], [], []}
|
||||
@@ -32,65 +43,85 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
By default, Elixir will try to automatically parse all switches.
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` will automatically
|
||||
be set to `true`.
|
||||
Switches without an argument, like `--debug` in the examples above, will
|
||||
automatically be set to `true`.
|
||||
|
||||
Note: Elixir also converts the switches to underscore atoms, so
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions. This means that option names on the command line cannot
|
||||
contain underscores; such options will be put in the invalid options
|
||||
list.
|
||||
## Options
|
||||
|
||||
## Switch Definitions
|
||||
The following options are supported:
|
||||
|
||||
* `:switches` or `:strict` - see the "Switch definitions" section below
|
||||
* `:aliases` - see the "Aliases" section below
|
||||
|
||||
## Switch definitions
|
||||
|
||||
Often it is better to explicitly list the known
|
||||
switches and their formats. The switches can be specified via two
|
||||
alternative options:
|
||||
switches and their formats. The switches can be specified via one of two
|
||||
options:
|
||||
|
||||
* `:switches` - defines some switches. An attempt is still made to parse
|
||||
switches that do not appear in the list.
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
* `:strict` - defines strict switches. Any switch in `argv` that is not
|
||||
specified in the list is returned in the invalid options list.
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that is not specified
|
||||
in the list is returned in the invalid options list.
|
||||
Both these options accept a keyword list of `{name, type}` tuples where `name`
|
||||
is an atom defining the name of the switch and `type` is an atom that
|
||||
specifies the type for the value of this switch (see the "Types" section below
|
||||
for the possible types and more information about type casting).
|
||||
|
||||
Note that you should only supply the `:switches` or `:strict` option.
|
||||
If you supply both, an error will be raised.
|
||||
If you supply both, an `ArgumentError` exception will be raised.
|
||||
|
||||
### Types
|
||||
|
||||
Option parser switches may take 0 or 1 argument.
|
||||
Switches parsed by `OptionParser` may take zero or one arguments.
|
||||
|
||||
The following switches take no argument:
|
||||
The following switches types take no arguments:
|
||||
|
||||
* `:boolean` - sets the value to true when given
|
||||
* `:count` - counts the number of times the switch is given
|
||||
* `:boolean` - sets the value to `true` when given (see also the
|
||||
"Negation switches" section below)
|
||||
* `:count` - counts the number of times the switch is given
|
||||
|
||||
The following switches take 1 argument:
|
||||
The following switches take one argument:
|
||||
|
||||
* `:integer` - parses the upcoming value as an integer.
|
||||
* `:float` - parses the upcoming value as a float.
|
||||
* `:string` - parses the upcoming value as a string.
|
||||
* `:integer` - parses the value as an integer
|
||||
* `:float` - parses the value as a float
|
||||
* `:string` - parses the value as a string
|
||||
|
||||
If a switch can't be parsed, it is returned in the invalid
|
||||
options list.
|
||||
If a switch can't be parsed according to the given type, it is returned
|
||||
in the invalid options list.
|
||||
|
||||
### Modifiers
|
||||
|
||||
Switches can be specified with modifiers, which change how
|
||||
they behave. The following modifiers are supported:
|
||||
|
||||
* `:keep` - keeps duplicated items instead of overriding them.
|
||||
Works with all types except `:count`.
|
||||
* `:keep` - keeps duplicated items instead of overriding them; works with
|
||||
all types except `:count`. Specifying `switch_name: :keep` assumes the
|
||||
type of `:switch_name` will be `:string`.
|
||||
|
||||
Note: if you want to use `:keep` with a non-string type, use a list, e.g.
|
||||
`[foo: [:integer, :keep]]`.
|
||||
Note that if you want to use `:keep` with a type other than `:string`, use a list
|
||||
as the type for the switch. For example: `[foo: [:integer, :keep]]`.
|
||||
|
||||
### Examples
|
||||
### Negation switches
|
||||
|
||||
Here are some examples of option parser working with different types
|
||||
and modifiers:
|
||||
In case a switch `SWITCH` is specified to have type `:boolean`, it may be
|
||||
passed as `--no-SWITCH` as well which will set the option to `false`:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be specified in the `:aliases` option:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples of working with different types and modifiers:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
@@ -121,21 +152,6 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file", "--unlock", "path/to/another/file"], strict: [unlock: :keep])
|
||||
{[unlock: "path/to/file", unlock: "path/to/another/file"], [], []}
|
||||
|
||||
### Negation switches
|
||||
|
||||
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
||||
which will set the option to `false`:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as options too:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -146,14 +162,16 @@ defmodule OptionParser do
|
||||
The same as `parse/2` but raises an `OptionParser.ParseError`
|
||||
exception if any invalid options are given.
|
||||
|
||||
If there weren't any errors, returns a three-element tuple as follows:
|
||||
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||
|
||||
1. parsed options,
|
||||
2. remaining arguments,
|
||||
3. empty list.
|
||||
* `parsed` is the list of parsed switches (same as in `parse/2`)
|
||||
* `rest` is the list of arguments (same as in `parse/2`)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse!(["--debug", "path/to/file"], strict: [debug: :boolean])
|
||||
{[debug: true], ["path/to/file"]}
|
||||
|
||||
iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer])
|
||||
** (OptionParser.ParseError) 1 error found!
|
||||
--limit : Expected type integer, got "xyz"
|
||||
@@ -172,7 +190,7 @@ defmodule OptionParser do
|
||||
@spec parse!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
case parse(argv, opts) do
|
||||
{parsed, argv, []} -> {parsed, argv}
|
||||
{parsed, args, []} -> {parsed, args}
|
||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||
end
|
||||
end
|
||||
@@ -201,14 +219,16 @@ defmodule OptionParser do
|
||||
The same as `parse_head/2` but raises an `OptionParser.ParseError`
|
||||
exception if any invalid options are given.
|
||||
|
||||
If there weren't any errors, returns a three-element tuple as follows:
|
||||
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||
|
||||
1. parsed options,
|
||||
2. remaining arguments,
|
||||
3. empty list.
|
||||
* `parsed` is the list of parsed switches (same as in `parse_head/2`)
|
||||
* `rest` is the list of arguments (same as in `parse_head/2`)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse_head!(["--source", "lib", "path/to/file", "--verbose"])
|
||||
{[source: "lib"], ["path/to/file", "--verbose"]}
|
||||
|
||||
iex> OptionParser.parse_head!(["--number", "lib", "test/enum_test.exs", "--verbose"], strict: [number: :integer])
|
||||
** (OptionParser.ParseError) 1 error found!
|
||||
--number : Expected type integer, got "lib"
|
||||
@@ -219,10 +239,10 @@ defmodule OptionParser do
|
||||
--verbose : Missing argument of type integer
|
||||
--source : Expected type integer, got "lib"
|
||||
"""
|
||||
@spec parse_head!(argv, options) :: {parsed, argv, errors} | no_return
|
||||
@spec parse_head!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
case parse_head(argv, opts) do
|
||||
{parsed, argv, []} -> {parsed, argv}
|
||||
{parsed, args, []} -> {parsed, args}
|
||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||
end
|
||||
end
|
||||
@@ -250,7 +270,7 @@ defmodule OptionParser do
|
||||
{:error, ["--" | rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg | rest]=remaining_args} ->
|
||||
{:error, [arg | rest] = remaining_args} ->
|
||||
# there is no option
|
||||
if all? do
|
||||
do_parse(rest, config, opts, [arg | args], invalid, all?)
|
||||
@@ -264,20 +284,20 @@ defmodule OptionParser do
|
||||
Low-level function that parses one option.
|
||||
|
||||
It accepts the same options as `parse/2` and `parse_head/2`
|
||||
as both functions are built on top of next. This function
|
||||
as both functions are built on top of this function. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||
successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the
|
||||
command line)
|
||||
(returned when the value cannot be parsed according to the switch type)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned in strict mode when the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
* `{:error, rest}` - there are no switches at the head of the given `argv`
|
||||
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
@@ -295,15 +315,15 @@ defmodule OptionParser do
|
||||
{:error, []}
|
||||
end
|
||||
|
||||
defp next(["--" | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["--" | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-" | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["-" | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["- " <> _ | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["- " <> _ | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
@@ -334,8 +354,9 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Receives a key-value enumerable and converts it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
Keys must be atoms. Keys with `nil` value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
(if the value is `true` or `false`, respectively),
|
||||
and all other values are converted using `to_string/1`.
|
||||
|
||||
## Examples
|
||||
@@ -364,6 +385,9 @@ defmodule OptionParser do
|
||||
@doc ~S"""
|
||||
Splits a string into argv chunks.
|
||||
|
||||
This function splits the given `string` into a list of strings in a similar
|
||||
way to many shells.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.split("foo bar")
|
||||
@@ -371,6 +395,7 @@ defmodule OptionParser do
|
||||
|
||||
iex> OptionParser.split("foo \"bar baz\"")
|
||||
["foo", "bar baz"]
|
||||
|
||||
"""
|
||||
@spec split(String.t) :: argv
|
||||
def split(string) do
|
||||
|
||||
@@ -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.
|
||||
|
||||
+21
-20
@@ -1,23 +1,24 @@
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info
|
||||
]}.
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info,
|
||||
{platform_define, "^18.*", old_map_specs}
|
||||
]}.
|
||||
|
||||
{yrl_opts, [
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
@@ -14,11 +14,16 @@
|
||||
-export_type([charlist/0, char_list/0, struct/0, as_boolean/1, keyword/0, keyword/1]).
|
||||
-type charlist() :: string().
|
||||
-type char_list() :: string().
|
||||
-type struct() :: #{'__struct__' => atom()}.
|
||||
-type as_boolean(T) :: T.
|
||||
-type keyword() :: [{atom(), any()}].
|
||||
-type keyword(T) :: [{atom(), T}].
|
||||
|
||||
-ifdef(old_map_specs).
|
||||
-type struct() :: #{'__struct__' => atom(), atom() => any()}.
|
||||
-else.
|
||||
-type struct() :: #{'__struct__' := atom(), atom() => any()}.
|
||||
-endif.
|
||||
|
||||
%% OTP Application API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
@@ -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.
|
||||
@@ -105,8 +105,6 @@ store_definition(Meta, Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepL
|
||||
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Module, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
{Name, Arity}.
|
||||
|
||||
%% @on_definition
|
||||
@@ -117,16 +115,6 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||
ok.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid ->
|
||||
Pid ! {struct_available, Module},
|
||||
ok
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
%% Retrieve location from meta file (if Key == keep)
|
||||
%% or @file, otherwise nil
|
||||
|
||||
@@ -218,7 +206,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, [], [], [], [], [], {[], []}).
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ import_function(Meta, Name, Arity, E) ->
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
||||
Required = is_element(Receiver, ?m(E, requires)),
|
||||
case is_element({Name, Arity}, get_macros(Receiver, Required)) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
|
||||
@@ -146,7 +147,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
{import, Receiver} ->
|
||||
case expand_require([{require, false} | Meta], Receiver, Tuple, Args, E) of
|
||||
case expand_require([{required, true} | Meta], Receiver, Tuple, Args, E) of
|
||||
{ok, _, _} = Response -> Response;
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
@@ -161,19 +162,15 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
Module = ?m(E, module),
|
||||
Required = (Receiver == ?m(E, module)) orelse is_element(Receiver, ?m(E, requires)) orelse required(Meta),
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
case is_element(Tuple, get_macros(Receiver, Required)) of
|
||||
true when Required ->
|
||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
true ->
|
||||
Requires = ?m(E, requires),
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
false ->
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), Requires}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info)
|
||||
end;
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), ?m(E, requires)}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info);
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
@@ -202,7 +199,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(
|
||||
@@ -220,8 +217,8 @@ caller(Line, E) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == {require, false}.
|
||||
required(Meta) ->
|
||||
lists:keyfind(required, 1, Meta) == {required, true}.
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
@@ -289,9 +286,20 @@ format_error({ambiguous_call, {Mod1, Mod2, Name, Arity}}) ->
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||
get_optional_macros(erlang) -> [];
|
||||
get_macros(erlang, _) -> [];
|
||||
|
||||
get_optional_macros(Receiver) ->
|
||||
get_macros(Receiver, false) ->
|
||||
case code:is_loaded(Receiver) of
|
||||
{file, _} ->
|
||||
try
|
||||
Receiver:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end;
|
||||
false -> []
|
||||
end;
|
||||
|
||||
get_macros(Receiver, true) ->
|
||||
case code:ensure_loaded(Receiver) of
|
||||
{module, Receiver} ->
|
||||
try
|
||||
|
||||
@@ -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} ->
|
||||
|
||||
@@ -31,7 +31,7 @@ local_for(Module, Name, Arity, Given) ->
|
||||
{Kind, Ann, [_ | _] = Clauses} when Given == nil; Kind == Given ->
|
||||
get_function(Ann, Module, Clauses);
|
||||
_ ->
|
||||
[_ | T] = erlang:get_stacktrace(),
|
||||
{current_stacktrace, [_ | T]} = erlang:process_info(self(), current_stacktrace),
|
||||
erlang:raise(error, undef, [{Module, Name, Arity, []} | T])
|
||||
end.
|
||||
|
||||
|
||||
@@ -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 ->
|
||||
@@ -31,7 +31,9 @@ expand_struct(Meta, Left, Right, #{context := Context} = E) ->
|
||||
end,
|
||||
|
||||
EMeta =
|
||||
case lists:member(ELeft, ?m(E, context_modules)) of
|
||||
%% We also include the current module because it won't be present
|
||||
%% in context module in case the module name is defined dynamically.
|
||||
case lists:member(ELeft, [?m(E, module) | ?m(E, context_modules)]) of
|
||||
true -> [{struct, context} | Meta];
|
||||
false -> Meta
|
||||
end,
|
||||
|
||||
@@ -525,7 +525,7 @@ add_beam_chunk(Bin, Id, ChunkData)
|
||||
%% the callback can't be expanded, invokes the given
|
||||
%% fun passing a possibly expanded AM:AF(Args).
|
||||
expand_callback(Line, M, F, Args, E, Fun) ->
|
||||
Meta = [{line, Line}, {require, false}],
|
||||
Meta = [{line, Line}, {required, true}],
|
||||
|
||||
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
|
||||
Fun(AM, AF, AA),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}}.
|
||||
|
||||
@@ -135,15 +135,15 @@ tokenize([], EndLine, _Column, #elixir_tokenizer{terminators=[{Start, {StartLine
|
||||
% Base integers
|
||||
|
||||
tokenize([$0, $x, H | T], Line, Column, Scope, Tokens) when ?is_hex(H) ->
|
||||
{Rest, Number, Length} = tokenize_hex([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_hex(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
tokenize([$0, $b, H | T], Line, Column, Scope, Tokens) when ?is_bin(H) ->
|
||||
{Rest, Number, Length} = tokenize_bin([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_bin(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) ->
|
||||
{Rest, Number, Length} = tokenize_octal([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_octal(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
% Comments
|
||||
@@ -413,8 +413,8 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
|
||||
|
||||
% Integers and floats
|
||||
|
||||
tokenize([H | _] = String, Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||
{Rest, Number, Length} = tokenize_number(String, [], false),
|
||||
tokenize([H | T], Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||
{Rest, Number, Length} = tokenize_number(T, [H], 1, false),
|
||||
tokenize(Rest, Line, Column + Length, Scope, [{number, {Line, Column, Column + Length}, Number} | Tokens]);
|
||||
|
||||
% Identifiers (including aliases)
|
||||
@@ -733,46 +733,55 @@ extract_heredoc_line(Marker, Rest, Buffer, _Counter) ->
|
||||
%% At this point, we are at least sure the first digit is a number.
|
||||
|
||||
%% Check if we have a point followed by a number;
|
||||
tokenize_number([$., H | T], Acc, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $. | Acc], true);
|
||||
tokenize_number([$., H | T], Acc, Length, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $. | Acc], Length + 2, true);
|
||||
|
||||
%% Check if we have an underscore followed by a number;
|
||||
tokenize_number([$_, H | T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Bool);
|
||||
tokenize_number([$_, H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Length + 2, Bool);
|
||||
|
||||
%% Check if we have e- followed by numbers (valid only for floats);
|
||||
tokenize_number([E, S, H | T], Acc, true)
|
||||
tokenize_number([E, S, H | T], Acc, Length, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number(T, [H, S, $e | Acc], true);
|
||||
tokenize_number(T, [H, S, $e | Acc], Length + 3, true);
|
||||
|
||||
%% Check if we have e followed by numbers (valid only for floats);
|
||||
tokenize_number([E, H | T], Acc, true)
|
||||
tokenize_number([E, H | T], Acc, Length, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $e | Acc], true);
|
||||
tokenize_number(T, [H, $e | Acc], Length + 2, true);
|
||||
|
||||
%% Finally just numbers.
|
||||
tokenize_number([H | T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Bool);
|
||||
tokenize_number([H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Length + 1, Bool);
|
||||
|
||||
%% Cast to float...
|
||||
tokenize_number(Rest, Acc, true) ->
|
||||
{Rest, list_to_float(lists:reverse(Acc)), length(Acc)};
|
||||
tokenize_number(Rest, Acc, Length, true) ->
|
||||
{Rest, list_to_float(lists:reverse(Acc)), Length};
|
||||
|
||||
%% Or integer.
|
||||
tokenize_number(Rest, Acc, false) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc)), length(Acc)}.
|
||||
tokenize_number(Rest, Acc, Length, false) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc)), Length}.
|
||||
|
||||
tokenize_hex([H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
||||
tokenize_hex([$_, H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
||||
tokenize_hex(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 16), length(Acc)}.
|
||||
tokenize_hex([H | T], Acc, Length) when ?is_hex(H) ->
|
||||
tokenize_hex(T, [H | Acc], Length + 1);
|
||||
tokenize_hex([$_, H | T], Acc, Length) when ?is_hex(H) ->
|
||||
tokenize_hex(T, [H | Acc], Length + 2);
|
||||
tokenize_hex(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 16), Length}.
|
||||
|
||||
tokenize_octal([H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
||||
tokenize_octal([$_, H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
||||
tokenize_octal(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 8), length(Acc)}.
|
||||
tokenize_octal([H | T], Acc, Length) when ?is_octal(H) ->
|
||||
tokenize_octal(T, [H | Acc], Length + 1);
|
||||
tokenize_octal([$_, H | T], Acc, Length) when ?is_octal(H) ->
|
||||
tokenize_octal(T, [H | Acc], Length + 2);
|
||||
tokenize_octal(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 8), Length}.
|
||||
|
||||
tokenize_bin([H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
||||
tokenize_bin([$_, H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
||||
tokenize_bin(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 2), length(Acc)}.
|
||||
tokenize_bin([H | T], Acc, Length) when ?is_bin(H) ->
|
||||
tokenize_bin(T, [H | Acc], Length + 1);
|
||||
tokenize_bin([$_, H | T], Acc, Length) when ?is_bin(H) ->
|
||||
tokenize_bin(T, [H | Acc], Length + 2);
|
||||
tokenize_bin(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 2), Length}.
|
||||
|
||||
%% Comments
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ expand(Meta, Args, E) ->
|
||||
|
||||
{ECases, EC} = lists:mapfoldl(fun expand/2, E, Cases),
|
||||
{EDoExpr, _} = elixir_exp:expand(DoExpr, EC),
|
||||
{EElseExpr, _} = expand_else(ElseExpr, E),
|
||||
{EElseExpr, _} = expand_else(Meta, ElseExpr, E),
|
||||
{{with, Meta, ECases ++ [[{do, EDoExpr} | EElseExpr]]}, E}.
|
||||
|
||||
expand({'<-', Meta, [Left, Right]}, E) ->
|
||||
@@ -55,25 +55,36 @@ expand({'<-', Meta, [Left, Right]}, E) ->
|
||||
expand(X, E) ->
|
||||
elixir_exp:expand(X, E).
|
||||
|
||||
expand_else(KV, E) when is_list(KV) ->
|
||||
expand_else(_Meta, KV, E) when is_list(KV) ->
|
||||
{[{do, EClauses}], EC} = elixir_exp_clauses:'case'([], [{do, KV}], E),
|
||||
{[{else, EClauses}], EC};
|
||||
expand_else(nil, E) ->
|
||||
{[], E}.
|
||||
expand_else(_Meta, nil, E) ->
|
||||
{[], E};
|
||||
expand_else(Meta, _KV, E) ->
|
||||
Message = "expected -> clauses for else in with",
|
||||
elixir_errors:compile_error(Meta, ?m(E, file), Message, []).
|
||||
|
||||
%% Translation
|
||||
|
||||
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 +101,20 @@ 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_for_match(Match)], Expr} ||
|
||||
{'->', Meta, [[Match], Expr]} <- Clauses].
|
||||
|
||||
error_match_for_match({'when', Meta, [Left, Right]}) ->
|
||||
{'when', Meta, [{error, Left}, Right]};
|
||||
error_match_for_match(Match) ->
|
||||
{error, Match}.
|
||||
|
||||
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}]}}.
|
||||
|
||||
@@ -3,30 +3,33 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmodule Sample do
|
||||
use Behaviour
|
||||
{_, _, sample_binary, _} =
|
||||
defmodule Sample do
|
||||
use Behaviour
|
||||
|
||||
defcallback first(integer) :: integer
|
||||
defcallback first(integer) :: integer
|
||||
|
||||
defcallback foo(atom(), binary) :: binary
|
||||
defcallback foo(atom(), binary) :: binary
|
||||
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
|
||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||
|
||||
defcallback orr(atom | integer) :: atom
|
||||
defcallback orr(atom | integer) :: atom
|
||||
|
||||
defcallback literal(123, {atom}, :atom, [integer], true) :: atom
|
||||
defcallback literal(123, {atom}, :atom, [integer], true) :: atom
|
||||
|
||||
defmacrocallback last(integer) :: Macro.t
|
||||
end
|
||||
defmacrocallback last(integer) :: Macro.t
|
||||
end
|
||||
|
||||
@sample_binary sample_binary
|
||||
|
||||
test "callbacks" do
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, guarded: 1, "MACRO-last": 2, literal: 5, orr: 1, foo: 2, bar: 2]
|
||||
end
|
||||
|
||||
test "specs" do
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 7
|
||||
assert length(Kernel.Typespec.beam_callbacks(@sample_binary)) == 7
|
||||
end
|
||||
|
||||
test "default is not supported" do
|
||||
|
||||
@@ -195,6 +195,8 @@ defmodule EnumTest do
|
||||
test "find_index/2" do
|
||||
assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
assert Stream.take(1..3, 3) |> Enum.find_index(fn _ -> false end) == nil
|
||||
assert Stream.take(1..6, 6) |> Enum.find_index(fn x -> x == 5 end) == 4
|
||||
end
|
||||
|
||||
test "find_value/2" do
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
defmodule Dialyzer.Macrocallback do
|
||||
@macrocallback required(atom) :: Macro.t
|
||||
@macrocallback optional(atom) :: Macro.t
|
||||
@optional_callbacks [optional: 1]
|
||||
end
|
||||
|
||||
defmodule Dialyzer.Macrocallback.Impl do
|
||||
@behaviour Dialyzer.Macrocallback
|
||||
defmacro required(var), do: Macro.expand(var, __CALLER__)
|
||||
defmacro optional(var), do: Macro.expand(var, __CALLER__)
|
||||
end
|
||||
@@ -1,4 +1,3 @@
|
||||
defmodule Bar do
|
||||
defstruct name: ""
|
||||
def foo?(%Foo{}), do: true
|
||||
defstruct name: "", foo: %Foo{}
|
||||
end
|
||||
|
||||
@@ -16,8 +16,14 @@ defmodule Kernel.DialyzerTest do
|
||||
|> Path.join("base_plt")
|
||||
|> String.to_charlist()
|
||||
|
||||
# Add a few key elixir modules for types
|
||||
files = Enum.map([Kernel, String, Keyword, Exception], &:code.which/1)
|
||||
# Some OSs (like Windows) do not provide the HOME environment variable.
|
||||
unless System.get_env("HOME") do
|
||||
System.put_env("HOME", System.user_home())
|
||||
end
|
||||
|
||||
# Add a few key elixir modules for types and macro functions
|
||||
mods = [Kernel, String, Keyword, Exception, Macro, Macro.Env, :elixir_env]
|
||||
files = Enum.map(mods, &:code.which/1)
|
||||
:dialyzer.run([analysis_type: :plt_build, output_plt: plt,
|
||||
apps: [:erts], files: files])
|
||||
|
||||
@@ -48,6 +54,7 @@ defmodule Kernel.DialyzerTest do
|
||||
{:ok, [outdir: dir, dialyzer: dialyzer]}
|
||||
end
|
||||
|
||||
@tag otp19: false
|
||||
test "no warnings on valid remote calls", context do
|
||||
copy_beam! context, Dialyzer.RemoteCall
|
||||
assert_dialyze_no_warnings! context
|
||||
@@ -63,6 +70,12 @@ defmodule Kernel.DialyzerTest do
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
test "no warnings on macrocallback", context do
|
||||
copy_beam! context, Dialyzer.Macrocallback
|
||||
copy_beam! context, Dialyzer.Macrocallback.Impl
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
defp copy_beam!(context, module) do
|
||||
name = "#{module}.beam"
|
||||
File.cp! Path.join(context[:base_dir], name),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -283,9 +283,10 @@ defmodule Kernel.RaiseTest do
|
||||
end
|
||||
|
||||
test "badfun error" do
|
||||
x = :example
|
||||
# Avoid "invalid function call" warning in >= OTP 19
|
||||
x = fn -> :example end
|
||||
result = try do
|
||||
x.(2)
|
||||
x.().(2)
|
||||
rescue
|
||||
x in [BadFunctionError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
@@ -455,6 +455,27 @@ defmodule Kernel.TypespecTest do
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with module attributes" do
|
||||
module = test_module do
|
||||
@keyword Keyword
|
||||
@type kw :: @keyword.t
|
||||
@type kw(value) :: @keyword.t(value)
|
||||
end
|
||||
|
||||
assert [type: {:kw, {:remote_type, _, [{:atom, _, Keyword}, {:atom, _, :t}, []]}, _},
|
||||
type: {:kw, {:remote_type, _, [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]]}, [{:var, _, :value}]}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "invalid remote @type with module attribute that does not evaluate to a module" do
|
||||
assert_raise CompileError, ~r/\(@foo is "bar"\)/, fn ->
|
||||
test_module do
|
||||
@foo "bar"
|
||||
@type t :: @foo.t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "defines_type?" do
|
||||
test_module do
|
||||
@type mytype :: tuple
|
||||
|
||||
@@ -310,6 +310,20 @@ defmodule Kernel.WarningTest do
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test "unused alias when also import" do
|
||||
assert capture_err(fn ->
|
||||
Code.compile_string """
|
||||
defmodule Sample do
|
||||
alias :lists, as: List
|
||||
import MapSet
|
||||
new()
|
||||
end
|
||||
"""
|
||||
end) =~ "unused alias List"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test "unused inside dynamic module" do
|
||||
import List, only: [flatten: 1], warn: false
|
||||
|
||||
@@ -421,8 +435,8 @@ defmodule Kernel.WarningTest do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string ~S"""
|
||||
defmodule Sample do
|
||||
def hello(arg \\ 0), do: nil
|
||||
def hello(arg), do: nil
|
||||
def hello(arg \\ 0), do: nil
|
||||
def hello(arg), do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "definitions with multiple clauses and default values require a function head"
|
||||
@@ -551,7 +565,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "undefined behaviour function foo/0 (for behaviour Sample1)"
|
||||
after
|
||||
purge [Sample1, Sample2, Sample3]
|
||||
purge [Sample1, Sample2]
|
||||
end
|
||||
|
||||
test "undefined macro for behaviour" do
|
||||
@@ -567,7 +581,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "undefined behaviour macro foo/0 (for behaviour Sample1)"
|
||||
after
|
||||
purge [Sample1, Sample2, Sample3]
|
||||
purge [Sample1, Sample2]
|
||||
end
|
||||
|
||||
test "undefined behavior" do
|
||||
|
||||
@@ -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)
|
||||
@@ -59,6 +66,7 @@ defmodule Kernel.WithTest do
|
||||
|
||||
test "else conditions" do
|
||||
assert with({:ok, res} <- 41, do: res, else: ({:error, error} -> error; res -> res + 1)) == 42
|
||||
assert with({:ok, res} <- 41, do: res, else: (res when res == 41 -> res + 1; res -> res)) == 42
|
||||
assert with({:ok, res} <- 41, do: res, else: (_ -> :error)) == :error
|
||||
end
|
||||
|
||||
@@ -68,6 +76,12 @@ defmodule Kernel.WithTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid else form" do
|
||||
assert_raise CompileError, "nofile:1: expected -> clauses for else in with", fn ->
|
||||
Code.eval_quoted(quote do: with(_ <- true, do: :ok, else: :error))
|
||||
end
|
||||
end
|
||||
|
||||
defp four() do
|
||||
4
|
||||
end
|
||||
|
||||
@@ -147,6 +147,16 @@ defmodule MapTest do
|
||||
%^struct{name: "john"} = user
|
||||
end
|
||||
|
||||
test "structs when using dynamic modules" do
|
||||
defmodule Module.concat(MapTest, DynamicUser) do
|
||||
defstruct [:name, :age]
|
||||
|
||||
def sample do
|
||||
%__MODULE__{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "structs when quoted" do
|
||||
assert (quote do
|
||||
%User{foo: 1}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -5,18 +5,24 @@ defmodule ProtocolTest do
|
||||
|
||||
doctest Protocol
|
||||
|
||||
defprotocol Sample do
|
||||
@type t :: any
|
||||
@doc "Ok"
|
||||
@spec ok(t) :: boolean
|
||||
def ok(term)
|
||||
end
|
||||
{_, _, sample_binary, _} =
|
||||
defprotocol Sample do
|
||||
@type t :: any
|
||||
@doc "Ok"
|
||||
@spec ok(t) :: boolean
|
||||
def ok(term)
|
||||
end
|
||||
|
||||
defprotocol WithAny do
|
||||
@fallback_to_any true
|
||||
@doc "Ok"
|
||||
def ok(term)
|
||||
end
|
||||
@sample_binary sample_binary
|
||||
|
||||
{_, _, with_any_binary, _} =
|
||||
defprotocol WithAny do
|
||||
@fallback_to_any true
|
||||
@doc "Ok"
|
||||
def ok(term)
|
||||
end
|
||||
|
||||
@with_any_binary with_any_binary
|
||||
|
||||
defprotocol Derivable do
|
||||
def ok(a)
|
||||
@@ -123,11 +129,11 @@ defmodule ProtocolTest do
|
||||
end
|
||||
|
||||
test "protocol defines callbacks" do
|
||||
assert get_callbacks(Sample, :ok, 1) ==
|
||||
[{:type, [11], :fun, [{:type, [11], :product, [{:user_type, [11], :t, []}]}, {:type, [11], :boolean, []}]}]
|
||||
assert get_callbacks(@sample_binary, :ok, 1) ==
|
||||
[{:type, 12, :fun, [{:type, 12, :product, [{:user_type, 12, :t, []}]}, {:type, 12, :boolean, []}]}]
|
||||
|
||||
assert get_callbacks(WithAny, :ok, 1) ==
|
||||
[{:type, [18], :fun, [{:type, [18], :product, [{:user_type, [18], :t, []}]}, {:type, [18], :term, []}]}]
|
||||
assert get_callbacks(@with_any_binary, :ok, 1) ==
|
||||
[{:type, 22, :fun, [{:type, 22, :product, [{:user_type, 22, :t, []}]}, {:type, 22, :term, []}]}]
|
||||
end
|
||||
|
||||
test "protocol defines functions and attributes" do
|
||||
@@ -182,8 +188,8 @@ defmodule ProtocolTest do
|
||||
assert Multi.test(:a) == :a
|
||||
end
|
||||
|
||||
defp get_callbacks(module, name, arity) do
|
||||
callbacks = for {:callback, info} <- module.__info__(:attributes), do: hd(info)
|
||||
defp get_callbacks(beam, name, arity) do
|
||||
callbacks = Kernel.Typespec.beam_callbacks(beam)
|
||||
List.keyfind(callbacks, {name, arity}, 0) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -308,6 +314,8 @@ defmodule Protocol.ConsolidationTest do
|
||||
{:ok, binary} = Protocol.consolidate(Sample, [Any, ImplStruct])
|
||||
:code.load_binary(Sample, 'protocol_test.exs', binary)
|
||||
|
||||
@sample_binary binary
|
||||
|
||||
# Any should be moved to the end
|
||||
:code.purge(WithAny)
|
||||
:code.delete(WithAny)
|
||||
@@ -367,7 +375,7 @@ defmodule Protocol.ConsolidationTest do
|
||||
end
|
||||
|
||||
test "consolidated keeps callbacks" do
|
||||
callbacks = for {:callback, info} <- Sample.__info__(:attributes), do: hd(info)
|
||||
callbacks = Kernel.Typespec.beam_callbacks(@sample_binary)
|
||||
assert callbacks != []
|
||||
end
|
||||
|
||||
|
||||
@@ -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,4 +1,10 @@
|
||||
ExUnit.start [trace: "--trace" in System.argv]
|
||||
exclude =
|
||||
case :erlang.system_info(:otp_release) do
|
||||
'19' -> [otp19: false]
|
||||
_ -> []
|
||||
end
|
||||
|
||||
ExUnit.start [exclude: exclude, trace: "--trace" in System.argv]
|
||||
|
||||
# Beam files compiled on demand
|
||||
path = Path.expand("../../tmp/beams", __DIR__)
|
||||
|
||||
@@ -15,7 +15,6 @@ extract_interpolations(String) ->
|
||||
Z
|
||||
end.
|
||||
|
||||
|
||||
% Interpolations
|
||||
|
||||
extract_interpolations_without_interpolation_test() ->
|
||||
|
||||
@@ -25,15 +25,16 @@ op_kw_test() ->
|
||||
[{atom, {1, 1, 5}, foo}, {dual_op, {1, 5, 6}, '+'}, {atom, {1, 6, 10}, bar}] = tokenize(":foo+:bar").
|
||||
|
||||
scientific_test() ->
|
||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1").
|
||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1"),
|
||||
[{number, {1, 1, 16}, 1.2345678e-7}] = tokenize("1_234.567_8e-10").
|
||||
|
||||
hex_bin_octal_test() ->
|
||||
[{number, {1, 1, 5}, 255}] = tokenize("0xFF"),
|
||||
[{number, {1, 1, 5}, 255}] = tokenize("0xF_F"),
|
||||
[{number, {1, 1, 6}, 255}] = tokenize("0xF_F"),
|
||||
[{number, {1, 1, 5}, 63}] = tokenize("0o77"),
|
||||
[{number, {1, 1, 5}, 63}] = tokenize("0o7_7"),
|
||||
[{number, {1, 1, 6}, 63}] = tokenize("0o7_7"),
|
||||
[{number, {1, 1, 5}, 3}] = tokenize("0b11"),
|
||||
[{number, {1, 1, 5}, 3}] = tokenize("0b1_1").
|
||||
[{number, {1, 1, 6}, 3}] = tokenize("0b1_1").
|
||||
|
||||
unquoted_atom_test() ->
|
||||
[{atom, {1, 1, 3}, '+'}] = tokenize(":+"),
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -367,18 +367,29 @@ defmodule ExUnit.Diff do
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[ins: ", ", ins: map_pair] | acc]
|
||||
end)
|
||||
result =
|
||||
if same == [] and altered == [] and missing != [] and surplus != [] do
|
||||
[[_ | elem_diff] | rest] = result
|
||||
[elem_diff | rest]
|
||||
else
|
||||
result
|
||||
end
|
||||
|
||||
result = Enum.reduce(surplus, result, fn({key, val}, acc) ->
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[del: ", ", del: map_pair] | acc]
|
||||
end)
|
||||
|
||||
result = Enum.reduce(altered, result, fn({key, {val1, val2}}, acc) ->
|
||||
value_diff = script_inner(val1, val2)
|
||||
[[{:eq, ", "}, {:eq, format_key(key, keyword?)}, value_diff] | acc]
|
||||
end)
|
||||
|
||||
result = Enum.reduce(same, result, fn({key, val}, acc) ->
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[eq: ", ", eq: map_pair] | acc]
|
||||
end)
|
||||
|
||||
[[_ | elem_diff] | rest] = result
|
||||
[{:eq, "%" <> name <> "{"}, [elem_diff | rest], {:eq, "}"}]
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -242,6 +242,8 @@ defmodule ExUnit.DiffTest do
|
||||
assert script(map2, map1) == expected
|
||||
assert script(map1, %{}) == [{:eq, "%{"}, [[del: "baz: 12"]], {:eq, "}"}]
|
||||
assert script(%{}, map1) == [{:eq, "%{"}, [[ins: "baz: 12"]], {:eq, "}"}]
|
||||
expected = [{:eq, "%{"}, [[del: "baz: 12"], [ins: "foo: 12"]], {:eq, "}"}]
|
||||
assert script(map1, %{foo: 12}) == expected
|
||||
|
||||
assert script(%{}, %{}) == [eq: "%{}"]
|
||||
end
|
||||
|
||||
@@ -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, ", ")}"
|
||||
@@ -535,6 +533,15 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `import_file` but only imports it if it is available.
|
||||
"""
|
||||
defmacro import_file_if_available(path) do
|
||||
quote do
|
||||
import_file(unquote(path), optional: true)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the contents of the file at `path` as if it were directly typed into
|
||||
the shell.
|
||||
@@ -587,6 +594,25 @@ defmodule IEx.Helpers do
|
||||
raise ArgumentError, "import_file/1 expects a literal binary as its argument"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calls `import/2` with the given arguments, but only if the module is available.
|
||||
|
||||
This lets you put imports in `.iex.exs` files (including `~/.iex.exs`) without
|
||||
getting compile errors if you open a console where the module is not available.
|
||||
|
||||
## Example
|
||||
|
||||
# In ~/.iex.exs
|
||||
import_if_available Ecto.Query
|
||||
"""
|
||||
defmacro import_if_available(quoted_module, opts \\ []) do
|
||||
module = Macro.expand(quoted_module, __CALLER__)
|
||||
|
||||
if Code.ensure_loaded?(module) do
|
||||
quote do: import unquote(quoted_module), unquote(opts)
|
||||
end
|
||||
end
|
||||
|
||||
# Compiles and loads an Erlang source file, returns {module, binary}
|
||||
defp compile_erlang(source) do
|
||||
source = Path.relative_to_cwd(source) |> String.to_charlist
|
||||
|
||||
@@ -108,7 +108,7 @@ defmodule IEx.Introspection do
|
||||
if docs = Code.get_docs(mod, :docs) do
|
||||
if doc = find_doc(docs, fun, arity) do
|
||||
if callback_module = is_nil(elem(doc, 4)) and callback_module(mod, fun, arity) do
|
||||
filter = &match?({^fun, _}, elem(&1, 0))
|
||||
filter = &match?({^fun, ^arity}, elem(&1, 0))
|
||||
print_callback_docs(callback_module, filter, &print_doc/2)
|
||||
else
|
||||
print_doc(doc)
|
||||
@@ -139,6 +139,8 @@ defmodule IEx.Introspection do
|
||||
end)
|
||||
end
|
||||
|
||||
defp has_content?(nil),
|
||||
do: false
|
||||
defp has_content?({_, _, _, _, false}),
|
||||
do: false
|
||||
defp has_content?({{name, _}, _, _, _, nil}),
|
||||
@@ -147,14 +149,11 @@ defmodule IEx.Introspection do
|
||||
do: true
|
||||
|
||||
defp callback_module(mod, fun, arity) do
|
||||
filter = &match?({{^fun, ^arity}, _}, &1)
|
||||
mod.module_info(:attributes)
|
||||
|> Keyword.get_values(:behaviour)
|
||||
|> Stream.concat()
|
||||
|> Enum.find(fn module ->
|
||||
module.module_info(:attributes)
|
||||
|> Enum.filter(&match?({:callback, _}, &1))
|
||||
|> Enum.any?(&match?({_, [{{^fun, ^arity}, _} | _]}, &1))
|
||||
end)
|
||||
|> Enum.find(&Enum.any?(Typespec.beam_callbacks(&1), filter))
|
||||
end
|
||||
|
||||
defp print_doc({{fun, _}, _line, kind, args, doc}) do
|
||||
|
||||
@@ -34,9 +34,11 @@ defmodule IEx.HelpersTest do
|
||||
test "h helper function" do
|
||||
pwd_h = "* def pwd()\n\nPrints the current working directory.\n\n"
|
||||
c_h = "* def c(files, path \\\\ \".\")\n\nCompiles the given files."
|
||||
eq_h = "* def ==(left, right)\n\nReturns `true` if the two items are equal.\n\n"
|
||||
|
||||
assert capture_io(fn -> h IEx.Helpers.pwd/0 end) =~ pwd_h
|
||||
assert capture_io(fn -> h IEx.Helpers.c/2 end) =~ c_h
|
||||
assert capture_io(fn -> h ==/2 end) =~ eq_h
|
||||
|
||||
assert capture_io(fn -> h IEx.Helpers.c/1 end) =~ c_h
|
||||
assert capture_io(fn -> h pwd end) =~ pwd_h
|
||||
@@ -77,14 +79,17 @@ defmodule IEx.HelpersTest do
|
||||
@doc "Docs for MyBehaviour.first"
|
||||
@callback first(integer) :: integer
|
||||
@callback second(integer) :: integer
|
||||
@callback second(integer, integer) :: integer
|
||||
end
|
||||
"""
|
||||
impl = """
|
||||
defmodule Impl do
|
||||
@behaviour MyBehaviour
|
||||
def first(0), do: 0
|
||||
@doc "Docs for Impl.second"
|
||||
@doc "Docs for Impl.second/1"
|
||||
def second(0), do: 0
|
||||
@doc "Docs for Impl.second/2"
|
||||
def second(0, 0), do: 0
|
||||
end
|
||||
"""
|
||||
files = ["my_behaviour.ex", "impl.ex"]
|
||||
@@ -92,10 +97,11 @@ defmodule IEx.HelpersTest do
|
||||
assert c(files) |> Enum.sort == [Impl, MyBehaviour]
|
||||
|
||||
assert capture_io(fn -> h Impl.first/1 end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
||||
assert capture_io(fn -> h Impl.second/1 end) == "* def second(int)\n\nDocs for Impl.second\n"
|
||||
assert capture_io(fn -> h Impl.second/1 end) == "* def second(int)\n\nDocs for Impl.second/1\n"
|
||||
assert capture_io(fn -> h Impl.second/2 end) == "* def second(int1, int2)\n\nDocs for Impl.second/2\n"
|
||||
|
||||
assert capture_io(fn -> h Impl.first end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
||||
assert capture_io(fn -> h Impl.second end) == "* def second(int)\n\nDocs for Impl.second\n"
|
||||
assert capture_io(fn -> h Impl.second end) == "* def second(int)\n\nDocs for Impl.second/1\n* def second(int1, int2)\n\nDocs for Impl.second/2\n"
|
||||
end
|
||||
after
|
||||
cleanup_modules([Impl, MyBehaviour])
|
||||
@@ -255,11 +261,17 @@ defmodule IEx.HelpersTest do
|
||||
end
|
||||
|
||||
test "import_file when the file is missing" do
|
||||
assert "nil" == capture_iex("import_file \"nonexistent\", optional: true")
|
||||
|
||||
failing = capture_iex("import_file \"nonexistent\"")
|
||||
assert "** (File.Error) could not read file" <> _ = failing
|
||||
assert failing =~ "no such file or directory"
|
||||
|
||||
assert "nil" == capture_iex("import_file_if_available \"nonexistent\"")
|
||||
end
|
||||
|
||||
test "import_if_available helper" do
|
||||
assert "nil" == capture_iex("import_if_available NoSuchModule")
|
||||
assert "[1, 2, 3]" == capture_iex("import_if_available Integer; digits 123")
|
||||
assert "[1, 2, 3]" == capture_iex("import_if_available Integer, only: [digits: 1]; digits 123")
|
||||
end
|
||||
|
||||
test "c helper" do
|
||||
|
||||
@@ -70,4 +70,3 @@ defmodule IEx.Case do
|
||||
|> String.trim
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -64,7 +64,7 @@ defmodule Mix.Dep do
|
||||
@doc """
|
||||
Returns loaded dependencies from the cache for the current environment.
|
||||
|
||||
Because the dependencies are cached during deps.check, their
|
||||
Because the dependencies are cached during deps.loadpaths, their
|
||||
status may be outdated (for example, `:compile` did not
|
||||
yet become `:ok`). Therefore it is recommended to not rely
|
||||
on their status, also given they haven't been checked
|
||||
@@ -225,16 +225,16 @@ defmodule Mix.Dep do
|
||||
def format_status(%Mix.Dep{app: app, status: {:divergedonly, other}} = dep) do
|
||||
recommendation =
|
||||
if Keyword.has_key?(other.opts, :only) do
|
||||
"Ensure the parent dependency specifies a superset of the child one in"
|
||||
"Ensure you specify at least the same environments in :only in your dep"
|
||||
else
|
||||
"Remove the :only restriction from"
|
||||
"Remove the :only restriction from your dep"
|
||||
end
|
||||
|
||||
"the dependency #{app}\n" <>
|
||||
"the :only option for dependency #{app}\n" <>
|
||||
"#{dep_status(dep)}" <>
|
||||
"\n does not match the environments calculated for\n" <>
|
||||
"\n does not match the :only option calculated for\n" <>
|
||||
"#{dep_status(other)}" <>
|
||||
"\n #{recommendation} your dep"
|
||||
"\n #{recommendation}"
|
||||
end
|
||||
|
||||
def format_status(%Mix.Dep{app: app, status: {:diverged, other}} = dep) do
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule Mix.Dep.Converger do
|
||||
if not diverged? && remote do
|
||||
# If there is a lock, it means we are doing a get/update
|
||||
# and we need to hit the remote converger which do external
|
||||
# requests and what not. In case of deps.check, deps and so
|
||||
# requests and what not. In case of deps.loadpaths, deps and so
|
||||
# on, there is no lock, so we won't hit this branch.
|
||||
lock = if lock_given?, do: remote.converge(deps, lock), else: lock
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ defmodule Mix.Task do
|
||||
# 2. Otherwise we look for it in dependencies.
|
||||
# 3. Finally, we compile the current project in hope it is available.
|
||||
module =
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.check") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Project.compile([]) end) ||
|
||||
get!(task)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@ defmodule Mix.Tasks.Clean do
|
||||
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,14 +90,14 @@ defmodule Mix.Tasks.Compile.App do
|
||||
best_guess
|
||||
end
|
||||
|
||||
properties = ensure_correct_properties(app, config, properties)
|
||||
|
||||
# Ensure we always prepend the standard application dependencies
|
||||
core_apps = [:kernel, :stdlib] ++ language_app(config)
|
||||
properties = Keyword.update!(properties, :applications, fn apps ->
|
||||
core_apps ++ apps
|
||||
[:kernel, :stdlib] ++ language_app(config) ++ apps
|
||||
end)
|
||||
|
||||
properties = ensure_correct_properties(app, config, properties)
|
||||
contents = {:application, app, properties}
|
||||
contents = {:application, app, properties}
|
||||
|
||||
Mix.Project.ensure_structure()
|
||||
File.write!(target, :io_lib.format("~p.", [contents]), [:utf8])
|
||||
|
||||
@@ -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
|
||||
@@ -93,8 +93,9 @@ defmodule Mix.Tasks.Compile do
|
||||
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
end
|
||||
|
||||
defp consolidate_protocols? do
|
||||
|
||||
@@ -44,10 +44,7 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
output = Mix.Project.consolidation_path(config)
|
||||
manifest = Path.join(output, @manifest)
|
||||
|
||||
protocols_and_impls =
|
||||
unless Mix.Project.umbrella?(config) do
|
||||
protocols_and_impls(config)
|
||||
end
|
||||
protocols_and_impls = protocols_and_impls(config)
|
||||
|
||||
cond do
|
||||
opts[:force] || Mix.Utils.stale?(Mix.Project.config_files(), [manifest]) ->
|
||||
@@ -79,10 +76,15 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
not scm.fetchable?,
|
||||
do: opts[:build])
|
||||
|
||||
app = Mix.Project.app_path(config)
|
||||
app =
|
||||
if Mix.Project.umbrella?(config) do
|
||||
[]
|
||||
else
|
||||
[Mix.Project.app_path(config)]
|
||||
end
|
||||
|
||||
protocols_and_impls =
|
||||
for path <- [app | deps] do
|
||||
for path <- app ++ deps do
|
||||
manifest_path = Path.join(path, ".compile.elixir")
|
||||
compile_path = Path.join(path, "ebin")
|
||||
Mix.Compilers.Elixir.protocols_and_impls(manifest_path, compile_path)
|
||||
@@ -120,10 +122,24 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
defp consolidate(protocol, paths, output, opts) do
|
||||
impls = Protocol.extract_impls(protocol, paths)
|
||||
reload(protocol)
|
||||
{:ok, binary} = Protocol.consolidate(protocol, impls)
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||
case Protocol.consolidate(protocol, impls) do
|
||||
{:ok, binary} ->
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||
end
|
||||
|
||||
# If we remove a dependency and we have implemented one of its
|
||||
# protocols locally, we will mark the protocol as needing to be
|
||||
# reconsolidated when the implementation is removed even though
|
||||
# the protocol no longer exists. Although most times removing a
|
||||
# dependency will trigger a full recompilation, such won't happen
|
||||
# in umbrella apps with shared build.
|
||||
{:error, :no_beam_info} ->
|
||||
remove_consolidated(protocol, output)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Unavailable #{inspect protocol}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -125,7 +133,8 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
end
|
||||
|
||||
try do
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-archives-check",
|
||||
"--no-elixir-version-check", "--no-warnings-as-errors"])
|
||||
:ok in List.wrap(res)
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -176,13 +185,14 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
Mix.raise "\"#{manager}\" installation failed"
|
||||
end
|
||||
|
||||
defp do_make(dep, config) do
|
||||
command = if match?({:win32, _}, :os.type) and File.regular?("Makefile.win") do
|
||||
"nmake /F Makefile.win"
|
||||
else
|
||||
"make"
|
||||
end
|
||||
do_command(dep, config, command, true)
|
||||
defp do_make(%{opts: opts} = dep, config) do
|
||||
command =
|
||||
if match?({:win32, _}, :os.type) and File.regular?(Path.join(opts[:dest], "Makefile.win")) do
|
||||
"nmake /F Makefile.win"
|
||||
else
|
||||
"make"
|
||||
end
|
||||
do_command(dep, config, command, true, [{"IS_DEP", "1"}])
|
||||
end
|
||||
|
||||
defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
defmodule Mix.Tasks.Deps.Check do
|
||||
defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
use Mix.Task
|
||||
|
||||
import Mix.Dep, only: [loaded_by_name: 2, format_dep: 1, ok?: 1,
|
||||
format_status: 1, check_lock: 1]
|
||||
|
||||
@moduledoc """
|
||||
Checks if all dependencies are valid, loading them along
|
||||
the way.
|
||||
Checks and loads all dependencies along the way.
|
||||
|
||||
If there is an invalid dependency, its status is printed
|
||||
before aborting.
|
||||
|
||||
This task is not shown in `mix help` but it is part
|
||||
of the `mix` public API and can be depended on.
|
||||
Although this task does not show up in `mix help`, it is
|
||||
part of Mix public API and can be depended on.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--no-archives-check` - do not check archives
|
||||
* `--no-deps-check` - do not check deps
|
||||
* `--no-deps-check` - do not check or compile deps, only load available ones
|
||||
* `--no-compile` - do not compile dependencies
|
||||
|
||||
"""
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
unless "--no-archives-check" in args do
|
||||
Mix.Task.run "archive.check", args
|
||||
end
|
||||
|
||||
all = Enum.map(Mix.Dep.cached(), &check_lock/1)
|
||||
|
||||
unless "--no-deps-check" in args do
|
||||
@@ -95,11 +89,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 +122,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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Deps.Unlock do
|
||||
|
||||
"""
|
||||
|
||||
@switches [all: :boolean, unused: :boolean]
|
||||
@switches [all: :boolean, unused: :boolean, filter: :string]
|
||||
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
@@ -29,6 +29,27 @@ defmodule Mix.Tasks.Deps.Unlock do
|
||||
opts[:unused] ->
|
||||
apps = Mix.Dep.loaded([]) |> Enum.map(& &1.app)
|
||||
Mix.Dep.Lock.read() |> Map.take(apps) |> Mix.Dep.Lock.write()
|
||||
filter = opts[:filter] ->
|
||||
lock = Mix.Dep.Lock.read
|
||||
apps = Map.keys(lock)
|
||||
|
||||
unlock =
|
||||
apps
|
||||
|> Enum.filter(&String.contains?("#{&1}", filter))
|
||||
|
||||
if unlock == [] do
|
||||
Mix.shell.error "warning: no dependencies were matched"
|
||||
else
|
||||
lock =
|
||||
Enum.reject(lock, fn({app, _}) ->
|
||||
app in unlock
|
||||
end)
|
||||
Mix.Dep.Lock.write(lock)
|
||||
Mix.shell.info """
|
||||
Unlocked deps:
|
||||
* #{Enum.join(unlock, "\n* ")}
|
||||
"""
|
||||
end
|
||||
|
||||
apps != [] ->
|
||||
lock =
|
||||
|
||||
+13
-17
@@ -6,6 +6,8 @@ defmodule Mix.Tasks.Do do
|
||||
@moduledoc """
|
||||
Executes the tasks separated by comma.
|
||||
|
||||
The comma should be followed by a space.
|
||||
|
||||
## Examples
|
||||
|
||||
The example below prints the available compilers and
|
||||
@@ -24,27 +26,21 @@ defmodule Mix.Tasks.Do do
|
||||
|
||||
@doc false
|
||||
def gather_commands(args) do
|
||||
gather_commands(args, [], [])
|
||||
gather_commands args, [], []
|
||||
end
|
||||
|
||||
defp gather_commands([], current, commands) do
|
||||
[current | commands]
|
||||
|> Enum.reject(&(&1 == []))
|
||||
|> Enum.map(&Enum.reverse(&1))
|
||||
|> Enum.reverse
|
||||
def gather_commands([head | rest], current, acc)
|
||||
when binary_part(head, byte_size(head), -1) == "," do
|
||||
part = binary_part(head, 0, byte_size(head) - 1)
|
||||
current = Enum.reverse([part | current])
|
||||
gather_commands rest, [], [current | acc]
|
||||
end
|
||||
|
||||
defp gather_commands([arg | rest], current, commands) do
|
||||
case String.split(arg, ",", parts: 2) do
|
||||
[arg] ->
|
||||
gather_commands(rest, [arg | current], commands)
|
||||
[left, right] ->
|
||||
rest = append_unless_empty(right, rest)
|
||||
current = append_unless_empty(left, current)
|
||||
gather_commands(rest, [], [current | commands])
|
||||
end
|
||||
def gather_commands([head | rest], current, acc) do
|
||||
gather_commands rest, [head | current], acc
|
||||
end
|
||||
|
||||
defp append_unless_empty("", list), do: list
|
||||
defp append_unless_empty(h, list), do: [h | list]
|
||||
def gather_commands([], current, acc) do
|
||||
Enum.reverse [Enum.reverse(current) | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Mix.Tasks.Escript do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "List installed escripts."
|
||||
@shortdoc "Lists installed escripts"
|
||||
|
||||
@moduledoc ~S"""
|
||||
Lists all installed escripts.
|
||||
|
||||
@@ -105,8 +105,9 @@ defmodule Mix.Tasks.Help do
|
||||
|
||||
# Loadpaths without checks because tasks may be defined in deps.
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
end
|
||||
|
||||
defp load_tasks() do
|
||||
|
||||
@@ -12,12 +12,13 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
## Command line options
|
||||
|
||||
* `--no-archives-check` - do not check archive
|
||||
* `--no-deps-check` - do not check dependencies (also implies --no-archives-check)
|
||||
* `--no-deps-check` - do not check dependencies
|
||||
* `--no-elixir-version-check` - do not check Elixir version
|
||||
|
||||
"""
|
||||
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
|
||||
def run(args) do
|
||||
config = Mix.Project.config
|
||||
|
||||
@@ -25,10 +26,14 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
check_elixir_version(config, args)
|
||||
end
|
||||
|
||||
# --no-deps is used only internally. It has not purpose
|
||||
# from Mix.CLI because the CLI itself already loads deps.
|
||||
unless "--no-deps" in args do
|
||||
Mix.Task.run "deps.check", args
|
||||
unless "--no-archives-check" in args do
|
||||
Mix.Task.run "archive.check", args
|
||||
end
|
||||
|
||||
# --no-deps is used only internally. It has no purpose
|
||||
# from Mix.CLI because running a task may load deps.
|
||||
unless "--no-deps" in args or System.get_env("MIX_NO_DEPS") in ~w(1 true) do
|
||||
Mix.Task.run "deps.loadpaths", args
|
||||
end
|
||||
|
||||
if config[:app] do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
@@ -67,6 +67,9 @@ defmodule Mix.Tasks.Test do
|
||||
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
||||
* `--stale` - run only tests which reference modules that changed since the
|
||||
last `test --stale`. You can read more about this option in the "Stale" section below.
|
||||
* `--listen-on-stdin` - run tests, and then listen on stdin. Receiving a newline will
|
||||
result in the tests being run again. Very useful when combined with `--stale` and
|
||||
external commands which produce output on stdout upon file system modification.
|
||||
|
||||
## Filters
|
||||
|
||||
@@ -164,7 +167,7 @@ defmodule Mix.Tasks.Test do
|
||||
exclude: :keep, seed: :integer, only: :keep, compile: :boolean,
|
||||
start: :boolean, timeout: :integer, raise: :boolean,
|
||||
deps_check: :boolean, archives_check: :boolean, elixir_version_check: :boolean,
|
||||
stale: :boolean]
|
||||
stale: :boolean, listen_on_stdin: :boolean]
|
||||
|
||||
@cover [output: "cover", tool: Cover]
|
||||
|
||||
@@ -172,6 +175,15 @@ defmodule Mix.Tasks.Test do
|
||||
def run(args) do
|
||||
{opts, files} = OptionParser.parse!(args, strict: @switches)
|
||||
|
||||
if opts[:listen_on_stdin] do
|
||||
System.at_exit fn _ ->
|
||||
IO.gets(:stdio, "")
|
||||
Mix.shell.info "Restarting..."
|
||||
:init.restart()
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
unless System.get_env("MIX_ENV") || Mix.env == :test do
|
||||
Mix.raise "\"mix test\" is running on environment \"#{Mix.env}\". If you are " <>
|
||||
"running tests along another task, please set MIX_ENV explicitly"
|
||||
|
||||
+225
-24
@@ -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,11 +150,16 @@ 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
|
||||
excludes = excludes()
|
||||
|
||||
each_source_entries(&source_warnings(&1, excludes), pair_fun)
|
||||
end
|
||||
|
||||
@@ -102,20 +167,38 @@ defmodule Mix.Tasks.Xref do
|
||||
source(runtime_dispatches: runtime_dispatches) = source
|
||||
|
||||
for {module, func_arity_lines} <- runtime_dispatches,
|
||||
exports = load_exports(module),
|
||||
{{func, arity}, lines} <- func_arity_lines,
|
||||
warning = unreachable_mfa(module, func, arity, lines, excludes),
|
||||
warning = unreachable_mfa(exports, module, func, arity, lines, excludes),
|
||||
do: warning
|
||||
end
|
||||
|
||||
defp unreachable_mfa(module, func, arity, lines, excludes) do
|
||||
defp load_exports(module) do
|
||||
if :code.is_loaded(module) do
|
||||
# If the module is loaded, we will use the faster function_exported?/3 check
|
||||
module
|
||||
else
|
||||
# Otherwise we get all exports from :beam_lib to avoid loading modules
|
||||
with file when is_list(file) <- :code.which(module),
|
||||
{:ok, {^module, [exports: exports]}} <- :beam_lib.chunks(file, [:exports]) do
|
||||
exports
|
||||
else
|
||||
_ -> :unknown_module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp unreachable_mfa(exports, module, func, arity, lines, excludes) do
|
||||
cond do
|
||||
excluded?(module, func, arity, excludes) ->
|
||||
nil
|
||||
skip?(module, func, arity) ->
|
||||
nil
|
||||
not Code.ensure_loaded?(module) ->
|
||||
exports == :unknown_module ->
|
||||
{Enum.sort(lines), :unknown_module, module, func, arity}
|
||||
not function_exported?(module, func, arity) ->
|
||||
is_atom(exports) and not function_exported?(module, func, arity) ->
|
||||
{Enum.sort(lines), :unknown_function, module, func, arity}
|
||||
is_list(exports) and not {func, arity} in exports ->
|
||||
{Enum.sort(lines), :unknown_function, module, func, arity}
|
||||
true ->
|
||||
nil
|
||||
@@ -210,7 +293,7 @@ defmodule Mix.Tasks.Xref do
|
||||
defp source_calls_for_filter(source, filter) do
|
||||
runtime_dispatches = source(source, :runtime_dispatches)
|
||||
compile_dispatches = source(source, :compile_dispatches)
|
||||
dispatches = Stream.concat(runtime_dispatches, compile_dispatches)
|
||||
dispatches = runtime_dispatches ++ compile_dispatches
|
||||
|
||||
calls =
|
||||
for {module, func_arity_lines} <- dispatches,
|
||||
@@ -279,12 +362,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
@@ -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
|
||||
|
||||
@@ -346,12 +346,12 @@ defmodule Mix.DepTest do
|
||||
refute_received {:mix_shell, :info, ["* Getting" <> _]}
|
||||
|
||||
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
|
||||
Mix.Tasks.Deps.Check.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
end
|
||||
|
||||
Mix.ProjectStack.clear_cache()
|
||||
Mix.env(:prod)
|
||||
Mix.Tasks.Deps.Check.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -496,7 +496,7 @@ defmodule Mix.DepTest do
|
||||
Mix.Tasks.Deps.run([])
|
||||
assert_received {:mix_shell, :info, ["* git_repo" <> _]}
|
||||
assert_received {:mix_shell, :info, [msg]}
|
||||
assert msg =~ "Ensure the parent dependency specifies a superset of the child one"
|
||||
assert msg =~ "Ensure you specify at least the same environments in :only in your dep"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -90,7 +90,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
File.rm_rf!("deps/git_repo/.git")
|
||||
|
||||
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
|
||||
Mix.Tasks.Deps.Check.run ["git_repo"]
|
||||
Mix.Tasks.Deps.Loadpaths.run ["git_repo"]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -126,7 +126,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
Code.delete_path("_build/dev/lib/git_repo/ebin")
|
||||
|
||||
# Deps on Git repo loads it automatically on compile
|
||||
Mix.Task.reenable "deps.check"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
Mix.Tasks.Deps.Compile.run ["deps_on_git_repo"]
|
||||
assert File.exists?("_build/dev/lib/deps_on_git_repo/ebin")
|
||||
end
|
||||
@@ -243,7 +243,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
# Update the lock and now we should get an error
|
||||
Mix.Dep.Lock.write %{git_repo: {:git, fixture_path("git_repo"), last, []}}
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
# Flush the errors we got, move to a clean slate
|
||||
|
||||
@@ -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=""]
|
||||
|
||||
@@ -121,13 +121,13 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end
|
||||
end
|
||||
|
||||
## deps.check
|
||||
## deps.loadpaths
|
||||
|
||||
test "checks list of dependencies and their status with success" do
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, ["* ok (https://github.com/elixir-lang/ok.git)"]}
|
||||
@@ -160,7 +160,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
File.rm_rf("_build")
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/ok/priv/sample")
|
||||
|
||||
@@ -174,7 +174,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Project.pop
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
@@ -185,7 +185,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Project.pop
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
refute File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
end
|
||||
@@ -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 ->
|
||||
@@ -224,6 +238,21 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "unlocks filtered deps", context do
|
||||
Mix.Project.push DepsApp
|
||||
in_tmp context.test, fn ->
|
||||
Mix.Dep.Lock.write %{git_repo: "abcdef", another: "hash", another_one: "hash"}
|
||||
Mix.Tasks.Deps.Unlock.run ["--filter", "another"]
|
||||
assert Mix.Dep.Lock.read == %{git_repo: "abcdef"}
|
||||
output = """
|
||||
Unlocked deps:
|
||||
* another
|
||||
* another_one
|
||||
"""
|
||||
assert_received {:mix_shell, :info, [^output]}
|
||||
end
|
||||
end
|
||||
|
||||
## Deps environment
|
||||
|
||||
defmodule DepsEnvApp do
|
||||
@@ -350,7 +379,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo in mix.exs is overriding a child dependency" <> _]}
|
||||
|
||||
@@ -371,7 +400,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" different specs were given for the git_repo app:" <> _ = msg]}
|
||||
@@ -402,7 +431,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo 0.1.0" <> _ = msg]}
|
||||
@@ -433,7 +462,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo in mix.exs is overriding" <> _]}
|
||||
@@ -489,7 +518,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
receive do
|
||||
@@ -509,7 +538,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
File.mkdir_p!("_build/dev/lib/ok/ebin")
|
||||
manifest_data = :erlang.term_to_binary({:v1, "the_future", :scm})
|
||||
@@ -521,8 +550,8 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
# deps.check will automatically recompile it
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
# deps.loadpaths will automatically recompile it
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, [^msg]}
|
||||
@@ -534,7 +563,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
File.mkdir_p!("_build/dev/lib/ok/ebin")
|
||||
manifest_data = :erlang.term_to_binary({:v1, System.version, :scm})
|
||||
@@ -546,8 +575,8 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
# deps.check will automatically recompile it
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
# deps.loadpaths will automatically recompile it
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
@@ -11,15 +11,11 @@ defmodule Mix.Tasks.DoTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "gather_command ignore spaces and trailing commas" do
|
||||
test "gather_command returns a list of commands" do
|
||||
import Mix.Tasks.Do, only: [gather_commands: 1]
|
||||
assert gather_commands(["compile", "--list,", "help"]) == [["compile", "--list"], ["help"]]
|
||||
assert gather_commands(["compile", "--list,help"]) == [["compile", "--list"], ["help"]]
|
||||
assert gather_commands(["help", ",compile", "--list"]) == [["help"], ["compile", "--list"]]
|
||||
assert gather_commands(["compile", "--list", ",", "help"]) == [["compile", "--list"], ["help"]]
|
||||
assert gather_commands(["help,", "compile", "--list"]) == [["help"], ["compile", "--list"]]
|
||||
assert gather_commands(["compile,", "run", "-e", "IO.puts :hello"]) == [["compile"], ["run", "-e", "IO.puts :hello"]]
|
||||
assert gather_commands(
|
||||
[",", "compile,", "run", "-e", "IO.puts :hello",",foo", "--bar", "--baz", ",", "baz,qux,abc", ","]) ==
|
||||
[["compile"], ["run", "-e", "IO.puts :hello"], ["foo", "--bar", "--baz"], ["baz"], ["qux"], ["abc"]]
|
||||
assert gather_commands(["compile,", "run", "-e", "[1, 2]"]) == [["compile"], ["run", "-e", "[1, 2]"]]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -104,6 +104,84 @@ defmodule Mix.Tasks.TestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: runs tests after input" do
|
||||
in_fixture "test_stale", fn ->
|
||||
port = mix_port(~w[test --stale --listen-on-stdin])
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
|
||||
:erlang.port_command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "No stale tests.", []) =~ "Restarting..."
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: does not exit on compilation failure" do
|
||||
in_fixture "test_stale", fn ->
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def f, do: error_not_a_var
|
||||
end
|
||||
""")
|
||||
|
||||
port = mix_port(~w[test --listen-on-stdin])
|
||||
|
||||
assert receive_until_match(port, "error", []) =~ "lib/b.ex"
|
||||
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def f, do: A.f
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
|
||||
File.write!("test/b_test_stale.exs", """
|
||||
defmodule BTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "f" do
|
||||
assert B.f() == error_not_a_var
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "undefined function error_not_a_var", []) =~ "test/b_test_stale.exs"
|
||||
|
||||
File.write!("test/b_test_stale.exs", """
|
||||
defmodule BTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "f" do
|
||||
assert B.f() == :ok
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
end
|
||||
end
|
||||
|
||||
defp receive_until_match(port, expected, acc) do
|
||||
receive do
|
||||
{^port, {:data, charlist}} ->
|
||||
string = to_string(charlist)
|
||||
acc = [acc | string]
|
||||
|
||||
if string =~ expected do
|
||||
IO.iodata_to_binary(acc)
|
||||
else
|
||||
receive_until_match(port, expected, acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp set_all_mtimes(time \\ {{2010, 1, 1}, {0, 0, 0}}) do
|
||||
Enum.each(Path.wildcard("**", match_dot: true), &File.touch!(&1, time))
|
||||
end
|
||||
|
||||
@@ -129,25 +129,46 @@ defmodule Mix.Tasks.XrefTest do
|
||||
|
||||
test "warnings: handles multiple modules in one file" do
|
||||
assert_warnings """
|
||||
defmodule A do
|
||||
defmodule A1 do
|
||||
def a, do: A2.no_func
|
||||
def b, do: A2.a
|
||||
end
|
||||
|
||||
defmodule A2 do
|
||||
def a, do: A.no_func
|
||||
def b, do: A.b
|
||||
def a, do: A1.no_func
|
||||
def b, do: A1.b
|
||||
end
|
||||
""", """
|
||||
warning: function A2.no_func/0 is undefined or private
|
||||
lib/a.ex:2
|
||||
|
||||
warning: function A.no_func/0 is undefined or private
|
||||
warning: function A1.no_func/0 is undefined or private
|
||||
lib/a.ex:7
|
||||
|
||||
"""
|
||||
end
|
||||
|
||||
test "warnings: doesn't load unloaded modules" do
|
||||
assert_warnings """
|
||||
defmodule A1 do
|
||||
@compile {:autoload, false}
|
||||
@on_load :init
|
||||
def init do
|
||||
raise "oops"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule A2 do
|
||||
def a, do: A1.no_func
|
||||
def b, do: A1.init
|
||||
end
|
||||
""", """
|
||||
warning: function A1.no_func/0 is undefined or private
|
||||
lib/a.ex:10
|
||||
|
||||
"""
|
||||
end
|
||||
|
||||
test "warnings: groups multiple warnings in one file" do
|
||||
assert_warnings """
|
||||
defmodule A do
|
||||
@@ -241,7 +262,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 +272,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 +295,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 +441,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 +452,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 +463,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 +477,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
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Mix.UmbrellaTest do
|
||||
|
||||
# Ensure we can compile and run checks
|
||||
Mix.Task.run "deps.compile"
|
||||
Mix.Task.run "deps.check"
|
||||
Mix.Task.run "deps.loadpaths"
|
||||
Mix.Task.run "compile", ["--verbose"]
|
||||
|
||||
assert_received {:mix_shell, :info, ["==> bar"]}
|
||||
@@ -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
|
||||
@@ -339,6 +364,53 @@ defmodule Mix.UmbrellaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "reconsolidates using umbrella parent information on shared _build" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defprotocol Bar do
|
||||
def bar(arg)
|
||||
end
|
||||
defimpl Bar, for: List do
|
||||
def bar(list), do: list
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build"], fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
refute Code.ensure_loaded?(Bar)
|
||||
end)
|
||||
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
Mix.Task.run("app.start")
|
||||
assert Protocol.consolidated?(Bar)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "reconsolidates using umbrella child information on shared _build" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defprotocol Bar do
|
||||
def foo(arg)
|
||||
end
|
||||
defimpl Bar, for: List do
|
||||
def foo(list), do: list
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
end)
|
||||
|
||||
# Emulate the dependency being removed
|
||||
Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build", deps: []], fn _ ->
|
||||
File.rm_rf "../../_build/dev/lib/bar"
|
||||
Mix.Task.run("compile.protocols")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defmodule Selective do
|
||||
def project do
|
||||
[apps_path: "apps",
|
||||
|
||||
@@ -136,6 +136,15 @@ defmodule MixTest.Case do
|
||||
env: envs) |> elem(0)
|
||||
end
|
||||
|
||||
def mix_port(args, envs \\ []) when is_list(args) do
|
||||
:erlang.open_port({:spawn_executable, elixir_executable()}, [
|
||||
{:args, ["-r", mix_executable(), "--" | args]},
|
||||
{:env, envs},
|
||||
:use_stdio,
|
||||
:stderr_to_stdout
|
||||
])
|
||||
end
|
||||
|
||||
defp mix_executable do
|
||||
Path.expand("../../../bin/mix", __DIR__)
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.3.0-rc.1"},
|
||||
{vsn, "1.3.2"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user