Skip to content

Add mix help Mod, mix help :mod, mix help Mod.fun and mix help Mod.fun/arity #14246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions lib/iex/lib/iex/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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".
Expand Down
30 changes: 18 additions & 12 deletions lib/iex/lib/iex/introspection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
22 changes: 22 additions & 0 deletions lib/mix/lib/mix/tasks/help.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ defmodule Mix.Tasks.Help do
Mix.raise("Unexpected arguments, expected \"mix help --search PATTERN\"")
end

def run([module = <<first, _::binary>>]) 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)
Expand Down
80 changes: 80 additions & 0 deletions lib/mix/test/mix/tasks/help_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
Loading