Skip to content

Commit b205181

Browse files
authored
Add mix help Mod, mix help :mod, mix help Mod.fun and mix help Mod.fun/arity (#14246)
1 parent b118698 commit b205181

File tree

4 files changed

+131
-16
lines changed

4 files changed

+131
-16
lines changed

lib/iex/lib/iex/helpers.ex

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ defmodule IEx.Helpers do
333333
"""
334334
defmacro open(term) do
335335
quote do
336-
IEx.Introspection.open(unquote(IEx.Introspection.decompose(term, __CALLER__)))
336+
IEx.Introspection.open(unquote(decompose(term, __CALLER__)))
337337
end
338338
end
339339

@@ -362,7 +362,7 @@ defmodule IEx.Helpers do
362362
"""
363363
defmacro h(term) do
364364
quote do
365-
IEx.Introspection.h(unquote(IEx.Introspection.decompose(term, __CALLER__)))
365+
IEx.Introspection.h(unquote(decompose(term, __CALLER__)))
366366
end
367367
end
368368

@@ -381,7 +381,7 @@ defmodule IEx.Helpers do
381381
"""
382382
defmacro b(term) do
383383
quote do
384-
IEx.Introspection.b(unquote(IEx.Introspection.decompose(term, __CALLER__)))
384+
IEx.Introspection.b(unquote(decompose(term, __CALLER__)))
385385
end
386386
end
387387

@@ -406,7 +406,7 @@ defmodule IEx.Helpers do
406406
"""
407407
defmacro t(term) do
408408
quote do
409-
IEx.Introspection.t(unquote(IEx.Introspection.decompose(term, __CALLER__)))
409+
IEx.Introspection.t(unquote(decompose(term, __CALLER__)))
410410
end
411411
end
412412

@@ -553,6 +553,13 @@ defmodule IEx.Helpers do
553553
dont_display_result()
554554
end
555555

556+
defp decompose(term, context) do
557+
case IEx.Introspection.decompose(term, context) do
558+
:error -> term
559+
m_mf_mfa -> Macro.escape(m_mf_mfa)
560+
end
561+
end
562+
556563
# Given any "term", this function returns all the protocols in
557564
# :code.get_path() implemented by the data structure of such term, in the form
558565
# of a binary like "Protocol1, Protocol2, Protocol3".

lib/iex/lib/iex/introspection.ex

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,44 +11,50 @@ defmodule IEx.Introspection do
1111
Decomposes an introspection call into `{mod, fun, arity}`,
1212
`{mod, fun}` or `mod`.
1313
"""
14-
def decompose({:/, _, [call, arity]} = term, context) do
14+
def decompose(atom, _context) when is_atom(atom), do: atom
15+
16+
def decompose({:__aliases__, _, _} = module, context) do
17+
Macro.expand(module, context)
18+
end
19+
20+
def decompose({:/, _, [call, arity]}, context) do
1521
case Macro.decompose_call(call) do
1622
{_mod, :__info__, []} when arity == 1 ->
17-
{:{}, [], [Module, :__info__, 1]}
23+
{Module, :__info__, 1}
1824

1925
{mod, fun, []} ->
20-
{:{}, [], [mod, fun, arity]}
26+
{Macro.expand(mod, context), fun, arity}
2127

2228
{fun, []} ->
23-
{:{}, [], [find_decompose_fun_arity(fun, arity, context), fun, arity]}
29+
{find_decompose_fun_arity(fun, arity, context), fun, arity}
2430

2531
_ ->
26-
term
32+
:error
2733
end
2834
end
2935

3036
def decompose(call, context) do
3137
case Macro.decompose_call(call) do
3238
{_mod, :__info__, []} ->
33-
Macro.escape({Module, :__info__, 1})
34-
35-
{mod, fun, []} ->
36-
{mod, fun}
39+
{Module, :__info__, 1}
3740

3841
{maybe_sigil, [_, _]} ->
3942
case Atom.to_string(maybe_sigil) do
4043
"sigil_" <> _ ->
41-
{:{}, [], [find_decompose_fun_arity(maybe_sigil, 2, context), maybe_sigil, 2]}
44+
{find_decompose_fun_arity(maybe_sigil, 2, context), maybe_sigil, 2}
4245

4346
_ ->
44-
call
47+
:error
4548
end
4649

50+
{mod, fun, []} ->
51+
{Macro.expand(mod, context), fun}
52+
4753
{fun, []} ->
4854
{find_decompose_fun(fun, context), fun}
4955

5056
_ ->
51-
call
57+
:error
5258
end
5359
end
5460

lib/mix/lib/mix/tasks/help.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,28 @@ defmodule Mix.Tasks.Help do
9494
Mix.raise("Unexpected arguments, expected \"mix help --search PATTERN\"")
9595
end
9696

97+
def run([module = <<first, _::binary>>]) when first in ?A..?Z or first == ?: do
98+
loadpaths!()
99+
100+
iex_colors = Application.get_env(:iex, :colors, [])
101+
mix_colors = Application.get_env(:mix, :colors, [])
102+
103+
try do
104+
Application.put_env(:iex, :colors, mix_colors)
105+
106+
module
107+
|> Code.string_to_quoted!()
108+
|> IEx.Introspection.decompose(__ENV__)
109+
|> case do
110+
:error -> Mix.raise("Invalid expression: #{module}")
111+
decomposition -> decomposition
112+
end
113+
|> IEx.Introspection.h()
114+
after
115+
Application.put_env(:iex, :colors, iex_colors)
116+
end
117+
end
118+
97119
def run([task]) do
98120
loadpaths!()
99121
opts = Application.get_env(:mix, :colors)

lib/mix/test/mix/tasks/help_test.exs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,86 @@ defmodule Mix.Tasks.HelpTest do
191191
end)
192192
end
193193

194+
test "help Elixir MODULE", context do
195+
in_tmp(context.test, fn ->
196+
output =
197+
capture_io(fn ->
198+
Mix.Tasks.Help.run(["Mix"])
199+
end)
200+
201+
assert output =~
202+
"Mix is a build tool that provides tasks for creating, compiling, and testing\nElixir projects, managing its dependencies, and more."
203+
end)
204+
end
205+
206+
test "help Elixir NESTED MODULE", context do
207+
in_tmp(context.test, fn ->
208+
output =
209+
capture_io(fn ->
210+
Mix.Tasks.Help.run(["IO.ANSI"])
211+
end)
212+
213+
assert output =~ "Functionality to render ANSI escape sequences."
214+
end)
215+
end
216+
217+
test "help Erlang MODULE", context do
218+
otp_docs? = match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:math))
219+
220+
in_tmp(context.test, fn ->
221+
output =
222+
capture_io(fn ->
223+
Mix.Tasks.Help.run([":math"])
224+
end)
225+
226+
if otp_docs? do
227+
assert output =~
228+
"This module provides an interface to a number of mathematical"
229+
else
230+
assert output =~ ":math was not compiled with docs"
231+
end
232+
end)
233+
end
234+
235+
test "help FUNCTION/0", context do
236+
in_tmp(context.test, fn ->
237+
output =
238+
capture_io(fn ->
239+
Mix.Tasks.Help.run(["DateTime.utc_now()"])
240+
end)
241+
242+
assert output =~ "Returns the current datetime in UTC"
243+
end)
244+
end
245+
246+
test "help FUNCTION/1", context do
247+
in_tmp(context.test, fn ->
248+
output =
249+
capture_io(fn ->
250+
Mix.Tasks.Help.run(["Enum.all?/1"])
251+
end)
252+
253+
assert output =~ "Returns `true` if all elements in `enumerable` are truthy."
254+
end)
255+
end
256+
257+
test "help NESTED MODULE FUNCTION/3", context do
258+
in_tmp(context.test, fn ->
259+
output =
260+
capture_io(fn ->
261+
Mix.Tasks.Help.run(["IO.ANSI.color/3"])
262+
end)
263+
264+
assert output =~ "Sets the foreground color from individual RGB values"
265+
end)
266+
end
267+
268+
test "help ERROR" do
269+
assert_raise Mix.Error, "Invalid expression: Foo.bar(~s[baz])", fn ->
270+
Mix.Tasks.Help.run(["Foo.bar(~s[baz])"])
271+
end
272+
end
273+
194274
test "help --search PATTERN", context do
195275
in_tmp(context.test, fn ->
196276
Mix.Tasks.Help.run(["--search", "deps"])

0 commit comments

Comments
 (0)