diff --git a/lib/iex/lib/iex/helpers.ex b/lib/iex/lib/iex/helpers.ex index 8964954aaab..907ab497ac8 100644 --- a/lib/iex/lib/iex/helpers.ex +++ b/lib/iex/lib/iex/helpers.ex @@ -333,7 +333,7 @@ defmodule IEx.Helpers do """ defmacro open(term) do quote do - IEx.Introspection.open(unquote(IEx.Introspection.decompose(term, __CALLER__))) + IEx.Introspection.open(unquote(decompose(term, __CALLER__))) end end @@ -362,7 +362,7 @@ defmodule IEx.Helpers do """ defmacro h(term) do quote do - IEx.Introspection.h(unquote(IEx.Introspection.decompose(term, __CALLER__))) + IEx.Introspection.h(unquote(decompose(term, __CALLER__))) end end @@ -381,7 +381,7 @@ defmodule IEx.Helpers do """ defmacro b(term) do quote do - IEx.Introspection.b(unquote(IEx.Introspection.decompose(term, __CALLER__))) + IEx.Introspection.b(unquote(decompose(term, __CALLER__))) end end @@ -406,7 +406,7 @@ defmodule IEx.Helpers do """ defmacro t(term) do quote do - IEx.Introspection.t(unquote(IEx.Introspection.decompose(term, __CALLER__))) + IEx.Introspection.t(unquote(decompose(term, __CALLER__))) end end @@ -553,6 +553,13 @@ defmodule IEx.Helpers do dont_display_result() end + defp decompose(term, context) do + case IEx.Introspection.decompose(term, context) do + :error -> term + m_mf_mfa -> Macro.escape(m_mf_mfa) + end + end + # Given any "term", this function returns all the protocols in # :code.get_path() implemented by the data structure of such term, in the form # of a binary like "Protocol1, Protocol2, Protocol3". diff --git a/lib/iex/lib/iex/introspection.ex b/lib/iex/lib/iex/introspection.ex index df587b02abe..48c99a20c9d 100644 --- a/lib/iex/lib/iex/introspection.ex +++ b/lib/iex/lib/iex/introspection.ex @@ -11,44 +11,50 @@ defmodule IEx.Introspection do Decomposes an introspection call into `{mod, fun, arity}`, `{mod, fun}` or `mod`. """ - def decompose({:/, _, [call, arity]} = term, context) do + def decompose(atom, _context) when is_atom(atom), do: atom + + def decompose({:__aliases__, _, _} = module, context) do + Macro.expand(module, context) + end + + def decompose({:/, _, [call, arity]}, context) do case Macro.decompose_call(call) do {_mod, :__info__, []} when arity == 1 -> - {:{}, [], [Module, :__info__, 1]} + {Module, :__info__, 1} {mod, fun, []} -> - {:{}, [], [mod, fun, arity]} + {Macro.expand(mod, context), fun, arity} {fun, []} -> - {:{}, [], [find_decompose_fun_arity(fun, arity, context), fun, arity]} + {find_decompose_fun_arity(fun, arity, context), fun, arity} _ -> - term + :error end end def decompose(call, context) do case Macro.decompose_call(call) do {_mod, :__info__, []} -> - Macro.escape({Module, :__info__, 1}) - - {mod, fun, []} -> - {mod, fun} + {Module, :__info__, 1} {maybe_sigil, [_, _]} -> case Atom.to_string(maybe_sigil) do "sigil_" <> _ -> - {:{}, [], [find_decompose_fun_arity(maybe_sigil, 2, context), maybe_sigil, 2]} + {find_decompose_fun_arity(maybe_sigil, 2, context), maybe_sigil, 2} _ -> - call + :error end + {mod, fun, []} -> + {Macro.expand(mod, context), fun} + {fun, []} -> {find_decompose_fun(fun, context), fun} _ -> - call + :error end end diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex index f52df49b33e..5fde52db513 100644 --- a/lib/mix/lib/mix/tasks/help.ex +++ b/lib/mix/lib/mix/tasks/help.ex @@ -94,6 +94,28 @@ defmodule Mix.Tasks.Help do Mix.raise("Unexpected arguments, expected \"mix help --search PATTERN\"") end + def run([module = <>]) when first in ?A..?Z or first == ?: do + loadpaths!() + + iex_colors = Application.get_env(:iex, :colors, []) + mix_colors = Application.get_env(:mix, :colors, []) + + try do + Application.put_env(:iex, :colors, mix_colors) + + module + |> Code.string_to_quoted!() + |> IEx.Introspection.decompose(__ENV__) + |> case do + :error -> Mix.raise("Invalid expression: #{module}") + decomposition -> decomposition + end + |> IEx.Introspection.h() + after + Application.put_env(:iex, :colors, iex_colors) + end + end + def run([task]) do loadpaths!() opts = Application.get_env(:mix, :colors) diff --git a/lib/mix/test/mix/tasks/help_test.exs b/lib/mix/test/mix/tasks/help_test.exs index 759ee34adf2..3670ea43dec 100644 --- a/lib/mix/test/mix/tasks/help_test.exs +++ b/lib/mix/test/mix/tasks/help_test.exs @@ -191,6 +191,86 @@ defmodule Mix.Tasks.HelpTest do end) end + test "help Elixir MODULE", context do + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["Mix"]) + end) + + assert output =~ + "Mix is a build tool that provides tasks for creating, compiling, and testing\nElixir projects, managing its dependencies, and more." + end) + end + + test "help Elixir NESTED MODULE", context do + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["IO.ANSI"]) + end) + + assert output =~ "Functionality to render ANSI escape sequences." + end) + end + + test "help Erlang MODULE", context do + otp_docs? = match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:math)) + + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run([":math"]) + end) + + if otp_docs? do + assert output =~ + "This module provides an interface to a number of mathematical" + else + assert output =~ ":math was not compiled with docs" + end + end) + end + + test "help FUNCTION/0", context do + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["DateTime.utc_now()"]) + end) + + assert output =~ "Returns the current datetime in UTC" + end) + end + + test "help FUNCTION/1", context do + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["Enum.all?/1"]) + end) + + assert output =~ "Returns `true` if all elements in `enumerable` are truthy." + end) + end + + test "help NESTED MODULE FUNCTION/3", context do + in_tmp(context.test, fn -> + output = + capture_io(fn -> + Mix.Tasks.Help.run(["IO.ANSI.color/3"]) + end) + + assert output =~ "Sets the foreground color from individual RGB values" + end) + end + + test "help ERROR" do + assert_raise Mix.Error, "Invalid expression: Foo.bar(~s[baz])", fn -> + Mix.Tasks.Help.run(["Foo.bar(~s[baz])"]) + end + end + test "help --search PATTERN", context do in_tmp(context.test, fn -> Mix.Tasks.Help.run(["--search", "deps"])